Chapter 7 — Sharing App
While working on my custom camera app, my Nikon COOLPIX S800c android camera arrived. I wanted to whip out a small useful app right away, so I've taken another diversion. This chapter will be adding a new sharing option that will make it simple to upload images to a PHP file upload server running on my local LAN.
I grabbed an example app that provides a Share target from this blog and the PHP upload code I already use in my camera app was modified from here. The trick is to add a special intent with filters the sharing mechanism will recognise and to notice how you are started in onCreate().
First, though, I had to slog through problems getting the camera to talk to the android debug bridge. I finally got an answer in this android discuss thread. So now I have this file:
# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT. # USE 'android update adb' TO GENERATE. # 1 USB VENDOR ID PER LINE. 0x04b0
That 0x04b0 is Nikon's USB vendor ID. As near as I can tell, this means the stock android SDK isn't yet aware that Nikon has produced a device running android, so I have to tell it manually via this mysterious undocumented file.
While I was fooling around with stuff like this, I added the camera to the USB storage quirks the same way I did the phone earlier (took a while to discover I need to have multiple quirks comma separated):
options usb-storage quirks=0489:c001:i,04b0:018f:i
At last I was able to connect adb to the camera and do logcat to get debug output from my hastily cobbled together app. I discovered it was actually working, but with the minor problem that I had forgotten to tell it I needed permission to talk to the network. A quick manifest edit and rebuild, and I was able to upload a file from the sharing menu of the gallery app to the same php script described back in Chapter 6.
Everything is hard coded at the moment. I need to add preferences to configure the server (or even multiple profiles for different servers) and a progress bar during upload would probably be nice, as would a nice icon, but the code does work:
I've renamed the app to Up With Php, added an icon (whipped out with the Simple Text icon creator app) and added preferences so you can set the server name and ask for the filename to be converted to lower case. This now makes it a marginally useful app. You can find the source code here:
I've also written up a users guide to the program with info about how to setup the php upload script.
There is still plenty to be done. It would obviously be simpler to use when connected to different LANs if I allowed you to name multiple profiles rather than having to edit the url every time you want to change. (And it might be extra spiffy to have it automatically pick the right profile based on location information — another opportunity to learn about something new).
It also seems silly to restrict it to just images. You might want to upload any kind of file. This gets kind of complicated though. I've found the openable category I (possibly) want in my Intent filter. I've found the android ContentResolver class and I can use it to get a ParcelFileDescriptor to read the data for any kind of Uri, but I'm having great difficulty trying to figure out how to find a filename to use in the arbitrary content case.
I also still need to add a progress bar, and error popups, and it would be nice if the app went away by itself once it finished the file upload.
Combining the recommendation I got in the link above with the code I already had gave me UpWithPhp-2012-10-09-21-24.tar.bz2, but I didn't really see any change in behavior for the things I found with share buttons, and the share button in the browser (for one example) didn't do anything at all.
So, I added a bunch of debug code along the lines of this note. That allowed me to discover than instead of getting Intent.EXTRA_STREAM in the Intent, I was getting Intent.EXTRA_TEXT, so I added a case for that which uploads the text string to a generated file name. That version is UpWithPhp-2012-10-10-19-08.tar.bz2, and finally has the full functionality I'm looking for (until I find a new app where the share button does something else and I need another case :-). In the browser case, this will upload a file containing the URL of the web page that was shared.
I added a bunch of Toast messages for error feedback in today's new version: UpWithPhp-2012-10-11-21-07.tar.bz2. Now you can get an idea of what went wrong without needing to hookup USB cables and run logcat.
DOH! All this time I've been calling run() on what I intended to be a separate thread rather than start(), but I haven't noticed because I've been sending small files. Now that I try to start a simple progress dialog and send some big movies I recorded, it is obvious my thread wasn't really there. I've fixed that, and also added a busy wait spinner while the upload is in progress now. That gives the vastly improved: UpWithPhp-2012-10-12-21-17.tar.bz2 version of the code.
Another problem fixed by this version is the way I was using the HttpURLConnection class for uploading the files. Turns out I needed to add the obscure call to setChunkedStreamingMode(), otherwise attempting to upload a big file tries to buffer the whole thing to generate a Content-Length header. That buffering is not gonna work when there is more data in the file than can possibly fit in memory, so I was crashing with out of memory errors before I made this change.
Continuing to improve things, I now have UpWithPhp-2012-10-13-15-22.tar.bz2. This adds a real progress bar in the case where I know how big the file is, not just a spinner (spinner still used for unknown size data). I've also tested uploading some fairly big files. A 5 minute long 1080p full HD video was a little over 500M and took about 5 minutes to upload on my local WiFi network (five seemed to be a popular number :-).
So, I was working on being able to cancel an upload, and I hit some of my Toast messages which then proceeded to cause a force close. I was apparently operating under the misconception that toast provided a way to get small message on the screen from anywhere, but no, not from anywhere. Most particularly not from my thread doing the file upload. I had to add a runOnUiThread() call to get the messages I wanted to display into the main activity so I could toast them there.
I did eventually add a cancel button to the progress dialog though, and hook it up to trigger a call to interrupt() the file upload thread, then in the loop writing the file to the web server, I check for the interrupted() flag and throw an exception to get out of there when the thread has been interrupted. That all seems to work, the upload server even times out and discards the partial upload, so no bad files wind up on the server when you cancel the upload, no special cleanup seems to be required.
The progress bar was also displaying the really arbitrary numbers I used to report progress. Since they made no sense, I wanted them gone, but the old API I'm trying to stick to didn't have an obvious way to disable them. Some searches eventually turned up this stackoverflow post. I turned it into the ProgressWithoutNumbers class you can find in the source code, and I no longer have meaningless numbers displayed during the upload.
You can find this latest source code here: UpWithPhp-2012-10-13-19-14.tar.bz2.
I've tweaked a few more things and added another preference for customizing the header that provides the filename to the php script. It defaults to the header expected by the script in the user manual, so there is little reason to change it, but if you want to interact with a different upload script (and can figure out the low level form-data headers to provide), then you can override the default. The format code %1$s should appear in the string somewhere, and it is replaced with the file name. This version is UpWithPhp-2012-10-13-22-09.tar.bz2.
I've really got this working well now. I added multiple profile support and it seems to function well. I learned a lot about using the sqlite database classes in android since the design I used for multiple profiles involved copying the shared preferences into and out of a database (with the current profile stored in the current shared preferences). I also had to figure out ListView and Adapter to present the list of defined profiles.
This version is UpWithPhp-2012-10-25-20-18.tar.bz2
I could do things like automatically try all the profiles if one doesn't work, or leave the GUI up on a failed upload and ask you to pick a different profile, but really it works quite well as it is, so I'm not planning any more work on it for a while. Another thing to maybe do someday is have it upload an error report when it gets intent extras it doesn't known how to upload (so I could possibly figure out how to support some new form of file sharing).
Finally have a reason to change it: After using it for a while I found that uploading two pictures in a row would often get an error on the second one. Searching the web, I found this similar bug and applied the same solution (turning off keep-alive). All my problems with uploading multiple images went away.
This final (until I change it again :-) version is UpWithPhp-2013-03-04-20-29.tar.bz2
What I learned
I've never used SQL much for anything, so discovering how to do things with it was a bit confusing. I think I finally got the hang of how a lot of the android SQL interfaces work though: If you put a ? character in a query, then a String from the String  args array will be substituted for the ? character. The first ? gets the first array entry, the second gets the second array entry, etc. Of course I also don't really know SQL command syntax, so I mostly rely on google queries to turn up an example of the sort of thing I want to do so I don't have to think too hard about how to write the SQL command I want :-).
The way you populate a ListView was also quite confusing. It took a while to understand that you need to define a separate view for the items that will appear in the list (which seems awful complicated until you figure out that allows you to make lists of pretty much anything, so the complication buys you a lot of flexibility). I'm still rather fuzzy on how the Adapter works if you have very complicated list entries, but at least I found some examples for the simple strings I was dealing with.