Friday, October 14, 2011

Using NFC in Android

Near Field Communication (NFC) is a technology for data exchange between devices in close proximity. It traces its root back to RFID, where a reader drives a passive electronic tag through a magnetic field. This is a typical use case for NFC, but it can also be used by two active devices to communicate with each other.

APIs for NFC have now been added to the Android SDK, and a few phones with the NFC capability are available. I have looked into how we could use NFC to improve an existing Android application we develop. The application is targeted for cities, and one of its features is to provide information about various places of public interest, and in some cases allow reports of malfunction to be sent through the application. This feature could make use of NFC by having tags posted at the places of interest, which would allow users to get directly to the information about the place by just tapping the phone on the tag. Another possibility would be to use Peer-to-Peer data exchange to let a user running the application tap another phone. If the other phone has the application installed, it would start the application on the same activity, otherwise the other phone may be redirected to the application on Android Market.

I looked more closely at the first use case of having tags at places of interest. One initial consideration is that the number of phones with NFC is still very small. It would therefore be desirable to have an alternative way to achieve the same functionality for other phones. For this reason I also had a look at using QR codes, which can be read through the camera in the phone.

When the Android system detects an NFC device, it searches for the most suitable application to process the data. NFC only works over a very short range, so having the user select the application to use from a list is only used as a last resort, since this would typically cause the user to move the phone away from the device, and thereby break the NFC connection. Instead, the NFC API has defined several types of intents with different priorities, and with different options to filter on tag content or technology. The highest priority is given to the foreground application, which can register itself to intercept any NFC intent. This is not a good option in our application, since it would require the user to start the application before tapping the phone on the tag.

The next highest priority is applications that filter on tag content, which would work well in this case. The content could be set to anything we want, since the tags would be made specifically for our application. A standard type of content which has support for filtering in Android is URIs. To ensure that our application gets the NFC intent we could use a non-standard scheme. The host part of the URI could be used to select the activity to use within the application, and an identifier could be included as a URI parameter:

abc://point?id=123

The intent filter for these NFC tags would look like this:

<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED" />
  <data android:host="point" android:scheme="abc" />
  <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

The same URI could also be encoded in a QR code. I used a bar code reader called ZXing which can easily be integrated with other applications. It can also do the reverse, i.e. generate a bar code image from text data which is useful for testing. There are two ways to use ZXing to get the scanned QR code into our application:
  1. Our application can send a specific intent defined by ZXing for integration. This starts the ZXing application and a result is returned when the scan is complete (or the scan was cancelled).
  2. The ZXing application is started and a QR code is scanned by the user. When the content is a URI, the user gets an option to open it. Our application can get the intent generated by this option by declaring an intent filter matching the URI.

For maximum usability, both ways could be implemented, allowing the user to proceed regardless of which application was started first. The way described in 1 above is described on the ZXing web page. The second way, which is what I tried, is quite simple. Our application declares an intent filter, which is very similar to the NFC intent filter above:

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <data android:host="point" android:scheme="abc" />
  <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

The URI can then be retrieved through the getData method on the received intent, in order to extract the id parameter, and this is used to find and display the desired information.

Some of the NFC features were introduced in API level 9, and the other were introduced in API level 10. The Android SDK develper guide describes how to declare an application to require this API level as well as NFC permission and hardware. This is however not what we want for our application. We want to use NFC if it is available, but the application should install and work without NFC and on old devices at lower API levels. The NFC permission must be declared to get NFC to work when it is available. The platform will use the permission declaration to implicitly derive the NFC hardware requirement, but the application can override this by explicitly declaring that NFC hardware is not required. Finally, the required API level can be kept at a low level while still allowing the application to use the NFC features in API level 10, by using the targetSdkVersion attribute. The result is the following declarations:

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<uses-sdk android:minsdkversion="4" android:targetsdkversion="10" />

The application can then be compiled with the API level 10 SDK, but can still be installed on anything with API level 4 or higher. In order to make use of NFC, the application will have to use classes in the android.nfc package, which is not available before API level 9. This may cause a  VerificationError on the application if it is not handled correctly. The solution is to put all dependencies on NFC into separate application classes that are only loaded on devices that actually support NFC. This works because the Dalvik VM only loads and verifies the application classes the first time any method or member in them are accessed. So even though some application classes would not compile on the API level of the device, the application will still run without errors as long as the classes using a higher API level are not touched. The application can check the API level of the device at runtime through the constant ”Build.VERSION.SDK_INT”. The application can also check if the NFC feature is available through the ”PacketManager.hasSystemFeature” method. Note that this method is only available from API level 5, so the API level must be checked first, and the feature check needs to be in a separate application class that is not loaded on lower API level devices.

The QR code alternative does not have the same problem with API levels (as the ZXing barcode reader only requires API level 3), but it will obviously only work if the barcode reader is installed. If the integration is implemented by starting the scan from ZXing, which then sends a ”VIEW” intent, then the application is not required to check anything. However in the other integration option, where the application sends an intent to the barcoder reader to initiate the scan, the application must check at runtime if the barcode reader is available. This check can be done with the ”PacketManager.queryIntentActivities” method. When the result of this check is negative, the application could disable any ”scan” button or anything else in the user interface used to initiate a scan operation. The application could also provide a link to the barcode reader in Android market to make it easy for the user to enable the QR code scanning feature. The link could be made like this:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("market://search?q=pname:com.google.zxing.client.android"));
startActivity(intent);

In summary, I found that the NFC capability could indeed be used to add a new feature to our application. It would make the application easier to use, and it could be implemented without limiting the availablility of the application to phones that have NFC hardware. If QR codes are used as a complement to NFC tags, then the same functionality could be implemented for phones without NFC.

/Mikael

3 comments: