Wednesday, April 27, 2011

Detecting the Keystore Signature in Code

Here's a problem I'm sure many of us have faced: you've got code that runs one way when you're developing and another way when you're releasing. A few examples I've run into:

- Logging during development, but disabled for release.
- Targeting development vs. production servers.
- MapView requires an API key tied to the signing key.

Before, I just setup static parameters in code that I'd have to remember to change before making a release build. It's a pain and error prone.

I've just determined a much better (and more automatic) way - detecting the signing keystore in code. Through use of the PackageManager you can actually determine the Signature of the app that's currently running.

First, you need to get the Signature of your release keystore. Put this code into your application temporarily, then sign it with your release keystore and run it:

PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
Log.i("test", pi.signatures[0].toCharsString());

Take the output from logcat and put it into this method:

private static final Signature SIG_RELEASE = new Signature("<YOUR SIGNATURE HERE>");

public static boolean isRelease(Context context) {
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature sig : pi.signatures) {
if (sig.equals(SIG_RELEASE)) {
return true;
}
}
}
catch (Exception e) {
Log.w("Exception thrown when detecting if app is signed by a release keystore.", e);

// Return true if we can't figure it out, just to play it safe
return true;
}

return false;
}

Now you can always call isRelease() and check whether your app was signed by your release keystore. No more having to edit files to create a release version of your app - just sign it with the correct keystore and you're ready to go!

Wednesday, April 13, 2011

HTML in TextViews

There's a trick I've been increasingly using as I've learned how powerful it is: HTML in TextViews.

I used to think that TextViews were pretty plain; they contained a CharSequence, and that was it. But it can actually contain a Spanned (or its mutable cousin, Spannable) buffer type as well. This means that there are a host of HTML tags that you can use to modify your TextView, which allows for different typefaces/functionality - all in the same TextView. Check out what you can do with just one string resource in a TextView:



Here's the string resource for above:

<string name="html">This <b>is</b> <i>all</i> <sub>in</sub> <u><sup>one</sup></u> <a href="http://google.com">TextView</a>.</string>

Android has tips on the basics of Html in TextViews, but it doesn't nearly cover some of the use cases. It also doesn't have a reference on which tags are supported (scroll to bottom for a reference I created).

Here are a few tips:

Links in TextViews

The simplest way to add links to TextView is to use the android:autoLink attribute. However, this only allows you to link visible URIs in the text and can sometimes lead to undesired situations (I had an app that was detecting the copyright dates "2009-2011" as a phone number). By using the <a> tag, you can create links with any text that leads to any URI:

<string name="my_site"><a href="http://idunnolol.com">My Website</a></string>

There's only one catch: when you try to use this in a TextView, it won't be clickable unless you set the movement method in code:

TextView myTextView = (TextView) findViewById(R.id.my_textview);
myTextView.setMovementMethod(LinkMovementMethod.getInstance());

Dynamic Html

All of the above examples have been with static text - that is, when the TextView's android:text attribute is set in XML from a string resource. What if you want to set a TextView's string resource in code? This is especially important when you use string formatting (which I'm a big fan of).

Android discusses this already, but for posterity I'll repeat the process here:

1. HTML-escape the string resource:

<string name="loud">Loud text here: &lt;b>%s&lt;/b></string>

2. Use Html.fromHtml() to dynamically create a Spanned that a TextView can be styled with:

Spanned spanned = Html.fromHtml(context.getString(R.string.loud, "this is loud"));
myTextView.setText(spanned);

Handling Custom Tags

So far so good, but I ran into a problem the other day: while the <strike> tag works if it's linked as a static string, it doesn't work when using Html.fromHtml(). I looked through the source code and discovered that Html.fromHtml() handles a different set of tags from static resources. Luckily, there's a way to handle tags that Html.fromHtml() doesn't: the TagHandler interface.

It's a little complex, but here's one possible solution for how to handle the <strike> tag. (My own solution differed a bit, but the essentials are the same.)

Tags Supported in String Resources

Tags in static string resources are parsed by android.content.res.StringBlock, which is a hidden class. I've looked through the class and determined which tags are supported:
  • <a> (supports attributes "href")
  • <annotation>
  • <b>
  • <big>
  • <font> (supports attributes "height", "size", "fgcolor" and "bicolor", as integers)
  • <i>
  • <li>
  • <marquee>
  • <small>
  • <strike>
  • <sub>
  • <sup>
  • <tt>
  • <u>

Tags Supported by Html.fromHtml()

For some reason, Html.fromHtml() handles a different set of of tags than static text supports. Here's a list of the tags (gleaned from Html.java's source code):
  • <a> (supports attribute "href")
  • <b>
  • <big>
  • <blockquote>
  • <br>
  • <cite>
  • <dfn>
  • <div>
  • <em>
  • <font> (supports attributes "color" and "face")
  • <i>
  • <img> (supports attribute "src". Note: you have to include an ImageGetter to handle retrieving a Drawable for this tag)
  • <p>
  • <small>
  • <strong>
  • <sub>
  • <sup>
  • <tt>
  • <u>

The font "color" attribute supports some color names (along with the normal integer-based color scheme):
  • aqua
  • black
  • blue
  • fuchsia
  • green
  • grey
  • lime
  • maroon
  • navy
  • olive
  • purple
  • red
  • silver
  • teal
  • white
  • yellow

Wednesday, April 6, 2011

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 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!