Don't Advertise Intent Filters That Are Not Yours

Subtitle: What Brown Should Not Be Doing For You

About 12 hours ago, somebody posted a question to StackOverflow that hits upon another Android anti-pattern: copying and pasting <intent-filters> without thinking.

The UPS Mobile app allows you to track packages and do a handful of other things that you might ordinarily do via the UPS Web site. It generally seems to be well-regarded, but it has an annoying flaw:

It claims to be Barcode Scanner, and does a lousy job at it.

Barcode Scanner, from ZXing, is a favorite among Android developers for its integration possibilities. However, some people do not like having a dependence upon the Barcode Scanner app, so they grab the open source code and attempt to blend it into their own apps. This is neither endorsed nor supported by the ZXing team, but since it is open source, it is also perfectly legitimate.

However, UPS (or whoever they hired to build the app) screwed up. They not only copied the source code, but they copied the manifest entry for the scanning activity. And, their activity has:

<intent-filter>
  <action android:name="com.google.zxing.client.android.SCAN" />
  <category android:name="android.intent.category.DEFAULT" />
<intent-filter>

This means that on any device that has UPS Mobile installed, they will be an option for handling Barcode Scanner Intents. What happened was that the person asking the question was manually invoking startActivityForResult() to bring up Barcode Scanner, was getting a chooser with UPS Mobile in it, and then was crashing upon choosing UPS Mobile… because UPS Mobile declared this activity to be not exported.

Due to this bug, Android will display non-exported activities in a chooser, despite the fact that they can never be successfully used by the user.

So, what should we learn from this?

First, UPS Mobile should not have used that <intent-filter>. As Dianne Hackborn has pointed out, your <intent-filter> mix is effectively part of your app’s API, and so you need to think long and hard about every <intent-filter> you publish. UPS Mobile is not Barcode Scanner and should not be advertising that they handle such Intents, despite the activity being not exported.

Second, UPS Mobile probably should not have had any <intent-filter> elements for this activity, if they intend to use it purely internally. They could just as easily use an explicit Intent to identify the activity and avoid all of this nonsense.

Third, the person who filed the SO question ideally would have been using ZXing’s IntentIntegrator. As Sean Owen of the ZXing project noted in a comment on my answer, IntentIntegrator ensures that only Barcode Scanner or official brethren will handle any scan requests, so this problem would not have appeared.

Fourth, Android really should not be showing non-exported activities in a chooser, which means probably that PackageManager should be filtering out non-exported activities from methods like queryIntentActivities(), which I presume lies at the heart of the chooser.


Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!