Programming NFC tagsJun 27, 2013

About This Document

This document is a developer’s guide on algorithms and mechanisms of programming NFC tags and reading written data from an NFC tag. To understand this document’s contents, basic Java and Android knowledge is required.

Introduction

Near Field Communication is a fairly new standard of close range radio communication. Unlike its predecessor, the RFID, it allows for a two way data exchange. In order to keep power consumption low, its range has been limited to about 10 cm (practical range is 4 cm). Communication can occur between two active devices, or between an active device and a passive NFC chip (a "tag").

Main uses

  • Contactless payment

  • Bluetooth/Wi-Fi connection setup

  • Task automation

  • Simple and quick data sharing

A few words about tags

Since multiple companies have developed their own NFC technologies, there are multiple types of NFC tags available on the market. Some offer only 90 bytes of writeable space with no extra functionalities and some offer several kilobytes, with built-in encoding and sectors requiring proprietary knowledge to write to. In Android, all these tag Technologies are obliged to (or, in some cases, recommended) to comply with TagTechnology interface specification. Tag type specific data can be exchanged with it using the byte[] transceive(byte[] data) method. Detailed specification of all tags is outside the scope of this article, so only the common algorithms will be described here. Two types of tags will be programmed in this example:

  • NfcA

  • MifareClassic

Permissions and preparation - AndroidManifest.xml

In order for an application to be able to use NFC hardware and software of a device, it must have the NFC permission, so add the following line to your AndroidManifest.xml:

<uses-permission android:name="android.permission.NFC" />

Since NFC is a fairly new technology, it will not work with older versions of Android - according to developer.android.com, minimum API level 10 is recommended:

<uses-sdk
    android:minSdkVersion="10"
    android:targetSdkVersion="17" />

If your application can’t do its work on non NFC-enabled devices, you can make sure that only compatible devices will see your app in the store:

<uses-feature
    android:name="android.hardware.nfc"
    android:required="true"  />
Intent filters

There are three NFC intents currently available in Android:

ACTION_NDEF_DISCOVERED - filter for this action if you want to respond to tags with an NDEF type payload. This is the most precise, recommended approach and has the highest priority of the three intents. Note that this intent will not be fired in case of an empty tag, so for a writer application, a more general action is required.

ACTION_TECH_DISCOVERED - filter for this action, if you want to respond to tags with any payload, if only the tag’s technology is known. Most empty tags will cause this intent to be sent when detected. This intent is second, regarding priority. In order to use this intent, a list of technologies supported by your activity must be created. For up to date details, see: http://developer.android.com/guide/topics/connectivity/nfc/nfc.html

ACTION_TAG_DISCOVERED - filter for this action, if you want to respond to any tags - even ones of unknown type. This might happen, if a tag has been released after the Android version installed on the detecting device. This intent has the lowest priority.

A simple example - introducing the NdefMessage and NdefRecord classes

The easiest, high level way of programming an NFC tag is sending a so-called NdefMessage to it. It is a simple class, containing (usually) an array of NdefRecord class objects. “Usually”, since writing a raw byte array is possible, although this array must be “parseable” to regular NdefRecords. Writing an NdefMessage into a tag consists of the following steps:

  1. 1. Create an array of NdefRecord objects, which will contain the data we want to write.
  2. 2. Create an NdefMessage, containing the above array (or separate NdefRecord objects).
  3. 3. Write the message into a tag (assuming that an appropriate tag has been detected).

Creating a basic text NdefRecord is quite simple:

new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), new byte[0], "Hello, NFC World!".getBytes())

And even simpler starting from API level 16:

NdefRecord.createMime("text/plain", DEFAULT_TEXT_MESSAGE.getBytes()));

Just as simple as wrapping it with an NdefMessage:

private NdefMessage mDefaultNdefTextMesssage = new NdefMessage(new NdefRecord[]{mNdefRecord});

Writing this message into an NFC tag requires only a little more effort.

Note

This example assumes that a tag supporting the android.nfc.tech.Ndef technology has been detected and that pMessage is a correct NdefMessage, which fits into this tag.

private boolean writeTag(final NdefMessage pMessage, final Tag pTag) {
...
    Ndef ndef = Ndef.get(pTag);

    try {
        if (ndef != null) {
            ndef.connect();
            if (messageSize > ndef.getMaxSize()) {
                logAndToast("Write failed - message size exceeds tag size");
                return false;
            }
            if (!ndef.isWritable()) {
                logAndToast("Write failed - tag is not writable");
                return false;
            }

            ndef.writeNdefMessage(pMessage);
            logAndToast("Write completed");
            return true;
        }
    } catch (IOException e) {
        logAndToast("Write failed - IOException: " + e.getMessage());
        e.printStackTrace();

        return false;
    } catch (FormatException e) {
        logAndToast("Write failed - FormatException: " + e.getMessage());
        e.printStackTrace();

        return false;
    }
[Code 1] A simple write to a tag.

What happens in the above code? Well, first an Ndef helper object is created for the tag. If the tag is not NDEF-compatible, this object will be null, so we need to check for that. Next, after a connection is made, two checks should be carried out. First to check if the tag can be written to and then, if the message will fit into it. Since the type-1 and type-2 tags have less than 100 bytes(!) of memory, this check is quite important. If everything seems ok, a write can be performed.

Important: writing to a tag is a blocking I/O operation, and should not be called inside UI thread!
If in the mean time the tag has left the vicinity of the device, IOException will be thrown. If the message’s format is incorrect, FormatException will be thrown.

Reading data from an NFC tag requires a bit more effort, due to the necessity of detecting data format and decoding it into usable structures:

public void onResume() {
    super.onResume();
    String tmp = “”;
    Intent nfcIntent = getIntent();
    setIntent(new Intent()); // consume Intent
    String action = nfcIntent.getAction();
    if (!(NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action) || NfcAdapter.ACTION_TECH_DISCOVERED.equals(action) || NfcAdapter.ACTION_TAG_DISCOVERED.equals(action))) {
        Log.w(TAG, "Non-NDEF action in intent - returning");
        return;
    }
      ...
    mTag = nfcIntent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    if (mTag != null) {
        Ndef ndefTag = Ndef.get(mTag);

        if (ndefTag != null) {
            NdefMessage ndefMesg = ndefTag.getCachedNdefMessage();
            if (ndefMesg != null) {
                for (NdefRecord ndr : ndefRecords) {
                    switch (pRecord.getTnf()) {
                    case NdefRecord.TNF_WELL_KNOWN :
                        tmp = parseWellKnownPayload(pRecord);
                        break;
                    ...
                }
            }
        }
    }
}

private String parseWellKnownPayload(NdefRecord pRecord) throws UnsupportedEncodingException {
    String ret = "";
    byte[] type = pRecord.getType();
    byte[] payload = pRecord.getPayload();

    if (Arrays.equals(type, NdefRecord.RTD_TEXT)) {
        if (payload.length > 0) {
            String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
            int languageCodeLength = payload[0] & 0077;
            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
            ret = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
        }
    }

    return ret;
}
[Code 2] Reading text/plain from NFC tag.

The algorithm to decipher the text is a bit complex, but apart from that everything is quite simple: read, check for null, convert, check for null, iterate, check for null and that’s it.

Writing to a non-formatted tag

When a tag does not support Ndef technology, but does support NdefFormatable, it has to be formatted in order for it to accept Ndef messages.

private boolean writeTag(final NdefMessage pMessage, final Tag pTag) throws IOException, FormatException {
    ...
    NdefFormatable nf = NdefFormatable.get(pTag);
    if (nf != null) {
        nf.connect();
        nf.format(pMessage);
    }
    ...
}
[Code 3] Formatting a NdefFormatable tag.

Note that not all tag types can be formatted so easily - more advanced tags require proprietary knowledge or security keys in order to perform a format correctly.

Writing and handling URL links

Just like before, we start with creating an NdefMessage:

...
public static final String DEFAULT_URL = "http://developer.samsung.com";
private NdefMessage mDefaultNdefURIMesssage = new NdefMessage(NdefRecord.createUri(DEFAULT_URL));
...

writeTag(mDefaultNdefURIMesssage, pTag);

...
[Code 4] Creating an URI message and writing it to a tag.

Since our URI is an http link, let’s open it in a browser upon detection.

private String parseWellKnownPayload(NdefRecord pRecord) throws UnsupportedEncodingException {
...
    byte[] type = pRecord.getType();

    if (Arrays.equals(type, NdefRecord.RTD_URI)) {
        Uri u = pRecord.toUri();
        Intent i = new Intent(Intent.ACTION_VIEW);
        i.setData(u);
        startActivity(i);

        return ret;
    }
...
}
[Code 5] Handling an URI message - opening a browser.

That’s all it takes to program a simple smart poster-like functionality into an NFC tag and its application.

Note

NdefRecord.toUri() method has been introduced in API level 16, so link will not be opened in a browser on devices with older OS.

Preventing future write operations on a tag

In some cases a tag owner wants to prevent any further write access to it, for instance it would not be productive to allow any NFC device owner to change the contents of a publically available smart poster tag, so that it advertises a competitor’s movie instead of our own for example. There are multiple software and hardware ways to achieve this.

The most simple (and crude) method is simply marking the entire tag as read only. Just call Ndef.makeReadonly() and no further modification of the tag possible. For obvious reasons, be careful while experimenting with this feature.

More advanced tags provide the programmer with authentication and selective lock mechanisms. For example, MIFARE Classic tags come with built-in programmable authentication keys, which can be used to restrict read or write access to a tag on a per sector basis.

Prioritizing with Android Application Records

Sometimes two or more applications filter for the same intent and you want to make sure that for a particular tag your application will have the priority. In order to do that, you need to send your application’s package name to the tag, as a part of NdefMessage.

Creating an AAR record is very simple:

NdefRecord.createApplicationRecord("com.samsung.nfcsample");

Simply write it as one of the records of NdefMessage - but not the first one. The first record is used to determine the type of the entire message (MIME type, URI, etc.), so it should contain your data. If you put AAR as the first record, your Activity will start with action = MAIN, instead of NDEF_DISCOVERED, which can be confusing.

One extremely useful feature of AAR is that upon scanning an NFC tag with an AAR message, when there is no application with a given package name on the device, Google Play is automatically started, so that the application in question can be downloaded.

Prioritizing with Foreground Dispatch System

Yet another method of making sure that your application will be the one to handle an intent is using the foreground dispatch system. The idea is simple - if your Activity is in the foreground, it should receive the intent, if it wants to, even if multiple activities happen to have registered as capable of handling it. In order to achieve this, a set of slightly unusual steps must be taken:

1. In your Activity’s onCreate() method, a PendingIntent must be declared, which will be used by Android to start the Activity.

PendingIntent mNfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

2. Create intent filters to determine, to which intents you want to claim priority on, when in foreground. Just like in AndroidManifest.xml, mime types/schemes have to be declared. The code below registers for intents with “text/plain” mime type.

IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
    ndefFilter.addDataType("text/plain");
} catch (MalformedMimeTypeException e) {
    e.printStackTrace();
}

IntentFilter[] mNdefFilters = new IntentFilter[]{ndefFilter};
[Code 6] Foreground dispatch - creating intent filters.

3. Enable the entire process by calling NfcAdapter’s method enableForegroundDispatch in onResume and disable it, by calling disableForegroundDispatch in onPause. This does mean that you could intercept intents even when not in foreground (by not calling the disable… method, for example), but that would be rude - don’t do it. On enabling, list the supported NFC tag technologies (or null for all of them).

public void onResume() {
    super.onResume();
    ...…
    String[][] techList = {
        new String[]{NfcA.class.getName()}, 
        new String[]{MifareClassic.class.getName()}};
    mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, mNdefFilters, techList);
}


public void onPause() {
    super.onPause();
    mNfcAdapter.disableForegroundDispatch(this);
}
[Code 7] Foreground dispatch - listing supported technologies.

Summary

As you can see, programming with NFC tags is quite simple and requires only limited resources. Naturally, since this is a developing technology, new algorithms and technologies (especially - new types of tags) are developed daily, so in order to stay up to date, an NFC programmer has to constantly improve his knowledge. Having said that, the benefits of the technology are enormous, and the world has just begun to embrace it, so it’s worth recommending to any serious programmer

Appendix A - List of Samsung NFC-enabled handsets (February 2013)

  • Galaxy Ace 2 (only some versions)

  • Galaxy Mini 2 (only some versions)

  • Galaxy Note (only some versions)

  • Galaxy Note II

  • Galaxy S Advance (only some versions)

  • Galaxy S Blaze 4G (only some versions)

  • Galaxy S II (only some versions)

  • Galaxy S III

  • Galaxy S III Mini

  • Galaxy S Relay 4G (only some versions)

  • S5230 (only some versions)

  • Wave 578 (only some versions)

  • Wave Y (only some versions)

Appendix B - Glossary

AAR - Android Application Record
NFC - Near Field Communication
NDEF - NFC Data Exchange Format