My application uses a foreground dispatching system so that the user can use their NFC tag to perform read-write operations in the tag.
This works well if the user correctly labels his tag (i.e. they click on it in the right place on the phone and leave it connected long enough), but if they physically delete the tag too soon, then ndef.writeNdefMessage(...)throws an IOException.
This means that the write operation failed, which is fair enough. But the real problem is that the same unsuccessful operation also removes all ndef formatting / message from the tag!
My code is built around fragments from Advanced NFC | Android Developers (unfortunately, the link to the ForegroundDispatch sample seems to be broken, and there is no such sample project for importing to Android Studio).
Step 1. Here is the result of logcat / stacktrace when the user first selects his NFC tag, but removes it too quickly:
03-28 20:15:18.589 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error
java.io.IOException
at android.nfc.tech.Ndef.writeNdefMessage(Ndef.java:320)
at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:170)
at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224)
at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946)
at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959)
at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968)
at android.app.ActivityThread.access$1700(ActivityThread.java:181)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6145)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
03-28 20:15:18.599 1481-17792/? E/SecNfcJni: nfaConnectionCallback: NFA_SELECT_RESULT_EVT error: status = 3
03-28 20:15:18.599 1481-1502/? E/SecNfcJni: reSelect: tag is not active
Step 2. Then the same user enters the same tag again, but it no longer contains the ndef message (which I confirmed by changing the code and checking that it ndef.getCachedNdefMessage()returns null):
03-28 20:15:27.499 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error
java.lang.Exception: Tag was not ndef formatted: android.nfc.action.TECH_DISCOVERED
at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:124)
at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224)
at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946)
at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959)
at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968)
at android.app.ActivityThread.access$1700(ActivityThread.java:181)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6145)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
, - Samsung Galaxy Core Prime ( ) Android 5.1.1 Samsung Galaxy A5 ( ) Android 5.0.2.
NFC, , (.. !), ...
- (. ) , ?
- ?
- NfcA IsoDep, Ndef?
, , , , , NFC, ?...
, , NXP MIFARE Ultralight (Ultralight C) - NTAG203 ( : ISO 14443-3A). ebay, Rapid NFC ( ), , , .
:
package com.example.exampleapp;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
public class NfcTestActivity extends AppCompatActivity {
private static String LOG_TAG = NfcTestActivity.class.getSimpleName();
private static int SUCCESS_COUNT = 0;
private static int FAILURE_COUNT = 0;
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter[] intentFiltersArray;
private String[][] techListsArray;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_test);
getSupportActionBar().setDisplayShowHomeEnabled(true);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
makeToast("NFC not available!", Toast.LENGTH_LONG);
finish();
}
else {
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[]{
ndef
};
techListsArray = new String[][]{
new String[]{
Ndef.class.getName()
}
};
}
}
@Override
public void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
}
@Override
public void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}
}
public void onNewIntent(Intent intent) {
Ndef ndef = null;
try {
String action = intent.getAction();
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
throw new Exception("Tag was not ndef formatted: " + action);
}
else {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
ndef = Ndef.get(tag);
if (ndef == null) {
throw new Exception("ndef == null!");
}
else {
ndef.connect();
NdefMessage ndefMessageOld = ndef.getCachedNdefMessage();
if (ndefMessageOld == null) {
throw new Exception("No ndef message on tag!");
}
else {
NdefRecord[] ndefRecordsOld = ndefMessageOld.getRecords();
int numRecords = (ndefRecordsOld == null) ? 0 : ndefRecordsOld.length;
NdefRecord[] ndefRecordsNew = new NdefRecord[numRecords];
for (int i = 0; i < numRecords; i++) {
ndefRecordsNew[i] = ndefRecordsOld[i];
}
NdefMessage ndefMessageNew = new NdefMessage(ndefRecordsNew);
ndef.writeNdefMessage(ndefMessageNew);
SUCCESS_COUNT++;
String msg = "Read & wrote " + numRecords + " records.";
makeToast(msg);
Log.d(LOG_TAG, msg);
}
}
}
}
catch(Exception e) {
FAILURE_COUNT++;
Log.e(LOG_TAG, "Tag error", e);
makeToast("Tag error: " + e, Toast.LENGTH_LONG);
}
finally {
try {
if (ndef != null) {
ndef.close();
}
}
catch(Exception e) {
Log.e(LOG_TAG, "Error closing ndef", e);
makeToast("Error closing ndef: " + e, Toast.LENGTH_LONG);
}
makeToast("Successes: " + SUCCESS_COUNT + ". Failures: " + FAILURE_COUNT);
}
}
private void makeToast(final String msg) {
makeToast(msg, Toast.LENGTH_SHORT);
}
private void makeToast(final String msg, final int duration) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
Toast.makeText(NfcTestActivity.this, msg, duration).show();
}
});
}
}