Image Sharing on Android

One of the sillier projects I've worked on in my spare time is Rage Faces for Android. It has almost no value to anyone, which is why it's free (in every sense of the word). However, it's been quite useful to me, as it's taught me a few things about sharing images on Android. Overall, I think this is a good reason for Android developers (or any developer, really) to have fun side projects to work on - there's almost no app so trivial that you can't learn something from doing it.

Here's three big ones I learned from working on Rage Faces.

Sharing Images

The first thing it taught me is that you need space on the SD card in order to share images. This makes sense, as sending images (typically) uses the ACTION_SEND intent along with a URI to the image. You can't link a URI to a resource inside your application package, so you have to put it somewhere else (I chose the SD card).

It's also possible to put the media into a ContentProvider using MediaStore.Images.Media.insertImage(). The downside is that the image is then inserted into the Gallery, which I didn't want just for sharing images (the whole point of the app is to avoid having to fill up your gallery with images you don't want, instead keeping them in one organized place). You can avoid putting images in the Gallery by putting them into a folder on the SD card and creating a file ".nomedia".

Picture Frame and ACTION_GET_CONTENT

ACTION_SEND can be used to send the images to other applications. The opposite of that is having the intent filter ACTION_GET_CONTENT enabled; that way, other applications can request data from your app. They both work essentially the same way, by passing a URI to the image in an Intent. In the case of ACTION_SEND, you pass the URI in EXTRA_STREAM. For ACTION_GET_CONTENT, it's actually provided in the return Intent's data.

However, there is one exception: the standard Picture Frame widget doesn't play by these rules. It uses ACTION_GET_CONTENT to allow anyone to hook into their system, but they won't read a stream URI. If you try it, the picture frame widget crashes. The relevant part of the crash log is here:

Caused by: java.lang.NullPointerException
     at com.cooliris.media.PhotoAppWidgetProvider$PhotoDatabaseHelper.setPhoto(PhotoAppWidgetProvider.java:121)
     at com.cooliris.media.PhotoAppWidgetConfigure.onActivityResult(PhotoAppWidgetConfigure.java:92)
     at android.app.Activity.dispatchActivityResult(Activity.java:3908)
     at android.app.ActivityThread.deliverResults(ActivityThread.java:2528)
     ... 11 more

What they expect is for you to pass an extra called "data", as a Bitmap. This means you need to pass back both the URI and the Bitmap as a Parcelable in the return Intent:

Intent return = new Intent();
return.setData(myImageUri);
return.putExtra("data", myBitmap);
setResult(RESULT_OK, return);
finish();

This neglect in following the rules of the road is, I assume, due to Cool Iris both writing the standard Picture Frame widget and the standard Gallery application. As a result, the two play together nicely, but you'll have to follow their rules to be part of the gang.

Messaging on HTC

There's another player who doesn't quite follow the rules: HTC Sense's messaging application. Instead of filtering on the ACTION_SEND action like everyone else, HTC Sense's messaging application listens to android.intent.action.SEND_MSG. This means two things for you as a developer: You have to test for the existence of an app that accepts the action android.intent.action.SEND_MSG, and then give the user an explicit option to send to HTC Sense's messaging application, in addition to the normal picker.

You can test whether the HTC Sense messaging app is on the phone by querying for the intent filter:

Intent dummy = new Intent("android.intent.action.SEND_MSG");
dummy.putExtra(Intent.EXTRA_STREAM, myImageUri);
dummy.setType("image/png");
PackageManager packageManager = getPackageManager();
List<ResolveInfo> list = packageManager.queryIntentActivities(dummy, PackageManager.MATCH_DEFAULT_ONLY);
if (list.size() > 0) { // This means the HTC Sense messaging app is on the phone }

If you do have HTC Sense, you'll have to give your users the option to send messages to it. This means another layer of dialogs, which is a pain to users. At this point, I think the best solution is to stop using the Android chooser altogether. As useful as it is, the Android ecosystem is too abused to keep using it. In an ideal world, you'd only need one Intent to pass your data around, but that's just not been the case. If you roll your own chooser (which is not particularly difficult), you can include multiple possible Intents to pass out.

It's also worth noting that HTC Sense's messaging system doesn't implement ACTION_GET_CONTENT, so you can't have users insert your app's content into a conversation already in progress. This is not an insignificant oversight - people have told me they switched text messaging apps because of this lacking feature!