How to Write Ndef Records to Nfc Tag

How to write NDEF records to NFC tag?

I would ignore what the Google documentation says for reading at https://developer.android.com/guide/topics/connectivity/nfc/nfc and for read/write at https://developer.android.com/guide/topics/connectivity/nfc/advanced-nfc#read-write as this provides a very poor user experience for writing to Tags and leads to a lot of failed writes because of user behaviour.

To get reliable writing to NFC with Android you should use the newer and much better enableReaderMode API https://developer.android.com/reference/android/nfc/NfcAdapter

Using this newer API leads to a lot less failed writes and corrupted cards because you can control when the notification sound happens. With the old Intent based system the system App pauses your app and then reads the card and makes a notification sound, the user then takes the card way before your App is resumed and has a chance to handle the card data and write to the card.

With the newer enableReaderMode API you turn off the system notification sound and your App is never paused to read the NFC card, you can then read and write to the card and then when you have successfully written to the card you can make the notification sound yourself.

Because any errors are silent, the user will keep trying to present the card until the success Notification. Additional logic is needed not to write the same message to every time a single card is presented or when different cards are presented.

Some example code (adapted from my app the does NFC low level reading and writing (not Ndef Tag technology))


public class NFCActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{

private NfcAdapter mNfcAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc);

mNfcAdapter = NfcAdapter.getDefaultAdapter(this);

// Rest of Activity setup
}

@Override
protected void onResume() {
super.onResume();

if(mNfcAdapter!= null) {
Bundle options = new Bundle();
// Work around for some broken Nfc firmware implementations that poll the card too fast
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);

// Enable ReaderMode for all types of card and disable platform sounds
mNfcAdapter.enableReaderMode(this,
this,
NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
}

}

@Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}

// This method is run in another thread when a card is discovered
// !!!! This method cannot cannot direct interact with the UI Thread
// Use `runOnUiThread` method to change the UI from this method
public void onTagDiscovered(Tag tag) {

// Read and or write to Tag here to the appropriate Tag Technology type class
// in this example the card should be an Ndef Technology Type
Ndef mNdef = Ndef.get(tag);

// Check that it is an Ndef capable card
if (mNdef!= null) {

// If we want to read
// As we did not turn on the NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
// We can get the cached Ndef message the system read for us.

NdefMessage mNdefMessage = mNdef.getCachedNdefMessage();

// Or if we want to write a Ndef message

// Create a Ndef Record
NdefRecord mRecord = NdefRecord.createTextRecord("en","English String");

// Add to a NdefMessage
NdefMessage mMsg = new NdefMessage(mRecord);

// Catch errors
try {
mNdef.connect();
mNdef.writeNdefMessage(mMsg);

// Success if got to here
runOnUiThread(() -> {
Toast.makeText(getApplicationContext(),
"Write to NFC Success",
Toast.LENGTH_SHORT).show();
});

// Make a Sound
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),
notification);
r.play();
} catch (Exception e) {
// Some error playing sound
}

} catch (FormatException e) {
// if the NDEF Message to write is malformed
} catch (TagLostException e) {
// Tag went out of range before operations were complete
} catch (IOException e){
// if there is an I/O failure, or the operation is cancelled
} finally {
// Be nice and try and close the tag to
// Disable I/O operations to the tag from this TagTechnology object, and release resources.
try {
mNdef.close();
} catch (IOException e) {
// if there is an I/O failure, or the operation is cancelled
}
}

}

}

How to read and write to NFC tags in NDEF format using C# compatible with Android?

As you already found, NFC Forum Type 2 tags (such as MIFARE Ultralight, NTAG, etc.) require an NDEF message to be embedded into a NDEF TLV (tag-length-value) structure. This means that you prepend the tag 03 and the length of the NDEF message to the message (value) itself. Thus, you get

+-----+--------+-------------------------+
| TAG | LENGTH | VALUE |
| 03 | 08 | D1 01 04 54 02 65 6E 31 |
+-----+--------+-------------------------+

In addition you may add a Terminator TLV (tag = FE, length = 00) to indicate that the remaining data area on the tag can be skipped from processing.

The NDEF library that you use only processes NDEF messages and not the container format that is needed for storing the data on an NFC tag. Thus, you need to process that part yourself.

Pack NDEF message into TLV structure

var msg = new NdefMessage { ... };
var msgBytes = msg.toByteArray();
var ndefTlvLen = new byte[(msgBytes.Length < 255) ? 1 : 3];
if (msgBytes.Length < 255) {
ndefTlvLen[0] = (byte)(msgBytes.Length);
} else {
ndefTlvLen[0] = (byte)0x0FF;
ndefTlvLen[1] = (byte)((msgBytes.Length >> 8) & 0x0FF);
ndefTlvLen[2] = (byte)(msgBytes.Length & 0x0FF);
}
var tagData = new byte[1 + ndefTlvLen.Length + msgBytes.Length + 2];
int offset = 0;
tagData[offset++] = (byte)0x003;
Array.Copy(ndefTlvLen, 0, tagData, offset, ndefTlvLen.Length);
offset += ndefTlvLen.Length;
Array.Copy(msgBytes, 0, tagData, offset, msgBytes.Length);
offset += msgBytes.Length;
tagData[offset++] = (byte)0x0FE;
tagData[offset++] = (byte)0x000;

Unpack NDEF message from TLV structure

var tagData = ...; // byte[]
var msg;
int offset = 0;
while (offset < tagData.Length) {
byte tag = tagData[offset++];
int len = (tagData[offset++] & 0x0FF);
if (len == 255) {
len = ((tagData[offset++] & 0x0FF) << 8);
len |= (tagData[offset++] & 0x0FF);
}
if (tag == (byte)0x03) {
var msgBytes = new byte[len];
Array.Copy(tagData, offset, msgBytes, 0, len);
msg = NdefMessage.FromByteArray(msgBytes);
} else if (tag == (byte)0xFE) {
break;
}
offset += len;
}

How to write NDEF message to any MIME type of NFC tag?

The NDEF_DISCOVERED intent filter can only be used for tags that already contain some (known) NDEF data type.

Instead, you could use the TECH_DISCOVERED intent filter to register for the discovery of just any Ndef or NdefFormatable tag (or any other tag type that best fits your needs):

<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />

With your xml/nfc_tech_filter.xml file looking like this:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.Ndef</tech>
</tech-list>
<tech-list>
<tech>android.nfc.tech.NdefFormatable</tech>
</tech-list>
</resources>

In your code, you would then, of course, need to replace the line

if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {

with

if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())) {

Alternatively (or in combination with the above), you could use the foreground dispatch method to just receive those NFC events while your activity is in the foreground (as opposed to being launched upon detection of any NDEF(-compatible) tag (that is not handled by an activity with a better matching NDEF_DISCOVERED filter), which could be annoying for the user. See this answer for further details on how to do this.

Writing data to an NFC tag that does not fit

The error message that you got in the log is pretty clear. Your NDEF message is too large to fit the data area of the tag. The obvious solution would be to use NFC tags with a sufficiently large storage capacity -- there's quite a few larger tags available. There's nothing else you could do about it if you want to store exactly that NDEF message.

However, there is ways to improve the NDEF message itself. You currently use a MIME type record to store a URL. That's definitely not the best choice of a record type to store a URL. (or actually any application-specific data). The MIME type that you chose costs ("application/" + context.getPackageName()).length() = 31 bytes (assuming your package name is 19 bytes).

  • If you used an NFC Forum external type record instead, you could create a much shorter type name of the form "mydomain.tld:appurl", so this would save quite a few bytes.

    relayRecord = NdefRecord.createExternal("mydomain.tld", "appurl", text.getBytes());
  • Since you want to store a URL, there's even a more efficient (and readily available) record type: the NFC Forum URI well-known type. The type name of that record consists only of the single letter "U", so this would already save 30 bytes compared to your MIME type record. Moreover, the URI record type uses a compression scheme to further reduce the size of the record: there is a list of well-known URI prefixes (e.g. "https://") that can be represented as a single byte in the URI record (the method NdefRecord.createUri() will automatically take care of that compression). Consequently, you would save another 7 bytes.

    relayRecord = NdefRecord.createUri(text);

2 NDEF messages/records on one NFC tag - Android

Two NDEF messages is not supported by Android (and probably also not by other platforms). Multiple NDEF records in one NDEF message works fine. As noted in the comments, an Android Application Record (AAR) works perfectly. Just keep in mind not to put it as the first record of the message and add an intent filter that matches the first record of the message (otherwise your app will be launched with normal LAUNCHER_MAIN, without NDEF message, instead of NDEF_DISCOVERED with NDEF message.

A URL pointing to the app in the Play Store works usually fine, too, if your combine it with an Intent filter for that URL in the app itself. It works also on Android 2.3. Keep in mind, though, that other apps beside the Play Store app and the browser may have a matching intent filter. You never have that problem with an AAR (but AAR is only supported since ICS).

Using NFC to open my application causes a separate version of it to be opened instead of the one that was open when RFID was found

You don't seem to be doing any foreground detection, really manifest entries are only used to start your App via NFC if not already running.

If your App is running you should use one of the 2 foreground detection API's, the old an not so reliable enableForegroundDispatch or the newer and better enableReaderMode

Here is an example of how use the better enableReaderMode

You can use both of these in conjunction with Manifest filters to handle NFC when your App is and is not running when the NFC Tag is presented.

Also note that your Manifest filter won't trigger for the NdefFormatable of your write method, your Manifest filter need to filter for ACTION_TECH_DISCOVERED and android.nfc.tech.NdefFormatable. See Here for details

Write data in NFC tag with the provided data

First of all: Stackoverflow is about programming and not the right place to get recommendations for tools and existing apps.

Nevertheless, I believe that parts of this question could be valuable for Android developers as well.

What you show in your question is an NDEF message. This NDEF message consists of a MIME type record and an external type record.

The MIME type record contains the MIME type "co/info" (atucally a malformed MIME type as it does not follow the corresponding RFC!) with the data value "bd://aa:bb:cc:dd:ee:ff". In an Android app, you could generate this record using the method NdefRecord.createMime():

NdefRecord r1 = NdefRecord.createMime("co/info",
"bd://aa:bb:cc:dd:ee:ff".getBytes("US-ASCII"));

The NFC Forum external type record is an Android Application Record (type name "android.com:pkg") for the application package "com.your.app.package". You can generate that record using the NdefRecord.createApplicationRecord() method:

NdefRecord r2 = NdefRecord.createApplicationRecord("com.your.app.package");

You can then assemble those two records into an NDEF message:

NdefMessage msg = new NdefMessage(r1, r2);

Finally, you can write that message to an NFC tag (assuming that you already obtained a tag handle from an NFC tag discovery intent and that the tag is already formatted for NDEF):

Ndef ndef = Ndef.get(tag);
ndef.connect();
ndef.writeNdefMessage(msg);
ndef.close();


Related Topics



Leave a reply



Submit