I have a basic countdown app that counts down from 10 secs when button is clicked. What would be the simplest method to have the timer start when contact is made with a specific NFC tag? (NTAG203)
I've read several NFC tutorials and the only one I've been able to get to work has been a simple tag reader. If I wanted to have a specific NFC tag trigger a simple timer app just like a button would, how would I go about doing so? Would it be best to try to read the ID or just interpret plain text as an identifier?
Thanks (Using Android Studio)
NFC app Main Activity (Code taken from this tutorial: http://shaikhhamadali.blogspot.com/2013/10/near-field-communication-nfc-android.html
package com.example.nfctest2.nfctest2app;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
public static final String TAG = "NfcTut";
private TextView tV_ReadNFC;
private NfcAdapter nfcAdapt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//initialize control
tV_ReadNFC = (TextView) findViewById(R.id.tV_ReadNFC);
//initialise nfc adapter
nfcAdapt = NfcAdapter.getDefaultAdapter(this);
//check is NFC adapter initialized null when device doesn't support NFC
if (nfcAdapt == null) {
// device deosn't support nfc
Toast.makeText(this, "your device doesn't support NFC.", Toast.LENGTH_SHORT).show();
finish();
return;
}
//check is NFC adapter feature enabled
if (!nfcAdapt.isEnabled()) {
tV_ReadNFC.setText("NFC is disabled.");
} else {
tV_ReadNFC.setText(R.string.attachNFCToRead);
}
handleIntent(getIntent());
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onPause() {
//Call this before onPause, to avoid an IllegalArgumentException.
stopForegroundDispatch(this, nfcAdapt);
super.onPause();
}
#Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}
private void handleIntent(Intent intent) {
//get action from intent
String action = intent.getAction();
//is action matches the NDEF_DISCOVERED
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
//what is the mime type
String type = intent.getType();
//is text plain or not
if (MIMETYPE_TEXT_PLAIN.equals(type)) {
//create tag instance and retrieve extended data from intent
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//execute background task
new NdefReaderBgTask().execute(tag);
} else {
Log.d(TAG, "mime type is not text/plain: " + type);
}
}
//is action matches the ACTION_TECH_DISCOVERED
else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
// In case we would still use the Tech Discovered Intent
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//get the available technologies
String[] techList = tag.getTechList();
//get class name
String searchedTech = Ndef.class.getName();
for (String tech : techList) {
//tag matched then execute background task
if (searchedTech.equals(tech)) {
new NdefReaderBgTask().execute(tag);
break;
}
}
}
}
/**
* #param act The corresponding {#link Activity} requesting the foreground dispatch.
* #param adp The {#link NfcAdapter} used for the foreground dispatch.
*/
public static void requestForegroundDispatch(final Activity act, NfcAdapter adp) {
//create instance of intent
final Intent intent = new Intent(act.getApplicationContext(), act.getClass());
//set flags on top
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
//crate instance of pending intent
final PendingIntent pendingIntent = PendingIntent.getActivity(act.getApplicationContext(), 0, intent, 0);
//create intent filters array
IntentFilter[] filters = new IntentFilter[1];
//create 2D array of techlist String
String[][] techList = new String[][]{};
// Note: This is the same filter as in our manifest.
filters[0] = new IntentFilter();
filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
filters[0].addCategory(Intent.CATEGORY_DEFAULT);
try {
//add data type
filters[0].addDataType(MIMETYPE_TEXT_PLAIN);
} catch (MalformedMimeTypeException e) {
//throw exception on different mime type
throw new RuntimeException("Check your mime type.");
}
//enable foreground dispatch to current activity
adp.enableForegroundDispatch(act, pendingIntent, filters, techList);
}
public static void stopForegroundDispatch(final Activity act, NfcAdapter adp) {
adp.disableForegroundDispatch(act);
}
private class NdefReaderBgTask extends AsyncTask<Tag, Void, String> {
#Override
protected String doInBackground(Tag... params) {
Tag tag = params[0];
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
// when NDEF is not supported by this Tag.
return null;
}
//Get the NdefMessage that was read from the tag at discovery time.
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
//Get the NDEF Records inside this NDEF Message.
NdefRecord[] records = ndefMessage.getRecords();
for (NdefRecord ndefRecord : records) {
if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) {
try {
return readNDEFRecordText(ndefRecord);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unsupported Encoding", e);
}
}
}
return null;
}
private String readNDEFRecordText(NdefRecord record) throws UnsupportedEncodingException {
// get record pay load variable length
byte[] payload = record.getPayload();
// Get the Text Encoding
String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16";
// Get the Language Code
int languageCodeLength = payload[0] & 0063;
// String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
// e.g. "en"
// Get the Text
return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
}
#Override
protected void onPostExecute(String result) {
if (result != null) {
tV_ReadNFC.setText("Content in your TAG: " + result);
}
}
}
}
Using the foreground dispatch to receive the NFC event is a good start.
Identifying a specific tag very much depends on what you need in your context:
You could match tag IDs. This is certainly the easiest approach as every tag exposes some ID that is used by the NFC device during tag enumeration and anti-collision. However, this approach has some limitations:
Not all tags expose static IDs. Some tags generate a fresh random ID upon power-up. Identifying such a tag from its ID is therefore impossible.
Depending on the protocol, IDs may have different lengths. E.g. an ISO 14443A (NfcA) tag may have a 4-byte, 7-byte or 10-byte UID. Thus, you have to consider this when creating your database of valid tags.
You can't auto-start your app based on a specific ID.
You have to record every allowed ID in some database and can't easily define a large set of allowed tags.
You could use a specific NDEF record on the tag. In my opinion this is the prefered way to go.
I would refrain from using the Text record type (or text/* MIME type records) as these types are supposed to be used to transport human-readable and (more importantly) human-interpreted text.
Instead, I would suggest that you define your own NFC Forum external type name (specifications are available for free from NFC Forum) and create a record using that type name.
This allows you to easily auto-start your (and only your) app by filtering for that particular type name.
You can start the countdown based on either presence of an NDEF record with that particular type name, or
you can add a specific data payload to your tags (e.g. an custom identifier) to allow only certain tags that have this type name to start the countdown or to distinguish between different tags carrying a record with that type name.
For the ID matching scenario, I would suggest that you modify the foreground dispatch activation like this (don't forget to actually activate the foreground dispatch in onResume()):
public static void requestForegroundDispatch(final Activity act, NfcAdapter adp) {
final Intent pendingIntent = PendingIntent.getActivity(act, 0, new Intent(act, act.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter[] filters = new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED) };
String[][] techList = new String[][] { new String[] { NfcA.class.getName() } };
adp.enableForegroundDispatch(act, pendingIntent, filters, techList);
}
This will match any ISO 14443 Type A tag (like the NTAG203).
Then you could retrieve the tag's ID in your handleIntent method like this:
private void handleIntent(Intent intent) {
String action = intent.getAction();
if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
byte[] id = tag.getId();
// do something with the ID
}
}
Related
I've been developing an application in Android Studio to read and write NFC tags, specifically Mifare Classic tags. I managed to develop and test it on my smartphone (with S.O. KitKat) in early 2016 (a year ago).
As I mentioned, leave aside the application, and after having updated the version of Android Studio, the SDK and the S.O. From my smartphone to MarshMallow, this error appears when trying to write to the label: "java.lang.NullPointerException: Attempt to invoke virtual method 'void android.nfc.tech.MifareClassic.connect()' on a null object reference".
This error is apparently generated when trying to connect to MifareClassic tag.
Attached the code of my activity replacing some parts with ... that I consider are irrelevant.
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NdefFormatable;
import android.os.Bundle;
import android.view.View.OnClickListener;
...
import java.io.IOException;
import java.io.UnsupportedEncodingException;
#SuppressLint("Escribir")
public class escribir extends Activity {
NfcAdapter adapter;
PendingIntent pendingIntent;
IntentFilter writeTagFilters[];
boolean writeMode;
Tag myTag;
MifareClassic mfc;
Context context;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_datospropietario);
context = this;
...
Button btnWrite = (Button)findViewById(R.id.button);
final String b = getIntent().getExtras().getString("datos");
btnWrite.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final String mensaje = (b + ...);
if (first.getText().toString().isEmpty()) {
Toast.makeText(context, context.getString(R.string.missing_fields), Toast.LENGTH_SHORT).show();
} else {
if (myTag == null) {
Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
} else {
MifareClassic tmpMFC = null;
try {
tmpMFC = MifareClassic.get(myTag);
} catch (Exception e) {
Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
e.printStackTrace();
}
mfc = tmpMFC;
int sect;
if (mfc != null) {
sect = mfc.getSectorCount();
}
try {
mfc.connect();
...
} catch (IOException e) {
Toast.makeText(context, context.getString(R.string.error_notag), Toast.LENGTH_LONG).show();
e.printStackTrace();
myTag = null;
}
}
}
});
adapter = NfcAdapter.getDefaultAdapter(this);
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
tagDetected.addCategory(Intent.CATEGORY_DEFAULT);
writeTagFilters = new IntentFilter[]{tagDetected};
}
private void write(String text, Tag tag, int sector) throws IOException, FormatException {
NdefRecord[] records = {createRecord(text), NdefRecord.createApplicationRecord("my_app")};
NdefMessage mensaje = new NdefMessage(records);
NdefFormatable formatable = NdefFormatable.get(tag);
if (formatable != null) {
formatable.connect();
formatable.format(mensaje);
formatable.close();
} else {
Ndef ndef = Ndef.get(tag);
ndef.connect();
ndef.writeNdefMessage(mensaje);
ndef.close();
}
MifareClassic mfc = MifareClassic.get(tag);
...
}
#SuppressLint("Escribir") private NdefRecord createRecord(String text) throws UnsupportedEncodingException{
String lang = "es";
byte[] textBytes = text.getBytes();
byte[] langBytes = lang.getBytes("US-ASCII");
int langLength = langBytes.length;
int textLength = textBytes.length;
byte[] payLoad = new byte[1 + langLength + textLength];
payLoad[0] = (byte) langLength;
System.arraycopy(langBytes, 0, payLoad, 1, langLength);
System.arraycopy(textBytes, 0, payLoad, 1 + langLength, textLength);
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payLoad);
}
#SuppressLint("Escribir") protected void onNewIntent(Intent intent){
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
}
}
public void onPause(){
super.onPause();
WriteModeOff();
}
public void onResume(){
super.onResume();
WriteModeOn();
}
#SuppressLint("Escribir") private void WriteModeOn(){
writeMode = true;
adapter.enableForegroundDispatch(this, pendingIntent, writeTagFilters, null);
}
#SuppressLint("Escribir") private void WriteModeOff(){
writeMode = false;
adapter.disableForegroundDispatch(this);
}
}
After researching the internet about this problem, I discovered that writing problems on NFC tags were presented on various devices, such as some HTC or Sony Xperia models. These issues were presented after upgrading the Android version to SDK 5.1 (Lollipop).
This problem originated because some of the manufacturers modified the stack order where the different Tag types were found causing conflicts when requesting TechExtras and returning SAK null or wrong values.
The detailed explanation I found for this is as follows:
HTC One: It seems, the reason for this bug is TechExtras of NfcA is null.
However, TechList contains MifareClassic.
Sony Xperia Z3 (+ emmulated MIFARE Classic tag): The buggy tag has two NfcA in the TechList with different SAK values and a MifareClassic (with the Extra of the second NfcA). Both, the second NfcA and the MifareClassic technique, have a SAK of 0x20. According to NXP's guidelines on identifying MIFARE tags (Page 11), this a MIFARE Plus or MIFARE DESFire tag. This method creates a new extra with the SAK values of both NfcA occurrences ORed (as mentioned in NXP's MIFARE type identification procedure guide) and replace the Extra of the first NfcA with the new one.
For more information please refer to https://github.com/ikarus23/MifareClassicTool/issues/52
And the patch proposed by the bildin user, who fixed my problem:
public Tag patchTag(Tag oTag)
{
if (oTag == null)
return null;
String[] sTechList = oTag.getTechList();
Parcel oParcel, nParcel;
oParcel = Parcel.obtain();
oTag.writeToParcel(oParcel, 0);
oParcel.setDataPosition(0);
int len = oParcel.readInt();
byte[] id = null;
if (len >= 0)
{
id = new byte[len];
oParcel.readByteArray(id);
}
int[] oTechList = new int[oParcel.readInt()];
oParcel.readIntArray(oTechList);
Bundle[] oTechExtras = oParcel.createTypedArray(Bundle.CREATOR);
int serviceHandle = oParcel.readInt();
int isMock = oParcel.readInt();
IBinder tagService;
if (isMock == 0)
{
tagService = oParcel.readStrongBinder();
}
else
{
tagService = null;
}
oParcel.recycle();
int nfca_idx=-1;
int mc_idx=-1;
for(int idx = 0; idx < sTechList.length; idx++)
{
if(sTechList[idx] == NfcA.class.getName())
{
nfca_idx = idx;
}
else if(sTechList[idx] == MifareClassic.class.getName())
{
mc_idx = idx;
}
}
if(nfca_idx>=0&&mc_idx>=0&&oTechExtras[mc_idx]==null)
{
oTechExtras[mc_idx] = oTechExtras[nfca_idx];
}
else
{
return oTag;
}
nParcel = Parcel.obtain();
nParcel.writeInt(id.length);
nParcel.writeByteArray(id);
nParcel.writeInt(oTechList.length);
nParcel.writeIntArray(oTechList);
nParcel.writeTypedArray(oTechExtras,0);
nParcel.writeInt(serviceHandle);
nParcel.writeInt(isMock);
if(isMock==0)
{
nParcel.writeStrongBinder(tagService);
}
nParcel.setDataPosition(0);
Tag nTag = Tag.CREATOR.createFromParcel(nParcel);
nParcel.recycle();
return nTag;
}
This patch was provided by bildin (https://github.com/bildin).
Although the device on which I tested was not of the earlier brands, the patch worked perfectly on my Moto X (1st Gen), with a modified ROM Marshmallow, so I guess it will also work for a wide range of devices with The NXP chip PN544
In my caste the NFC Tag had to be formatted first.
String[] techList = tag.getTechList();
did not contain the required tech:
TagTechnology.NDEF
but
TagTechnology.NDEF_FORMATABLE
was listed. I formatted the Tag like:
NdefFormatable ndefFormatable = NdefFormatable.get(tag);
ndefFormatable.connect();
ndefFormatable.format(message);
ndefFormatable.close();
The error might be when you're doing:
int sect = mfc.getSectorCount();
Since mfc might be null after doing
MifareClassic mfc = MifareClassic.get(myTag);
MifareClassic mfc = MifareClassic.get(myTag);
The mfc object is created is created only if the card techlist has android.nfc.tech.MifareClassic, otherwise returns null for MifareClassic object.
You may check the techlist by myTag.getTechList();
Use the card that has the above stated tech in it's techlist and you are good to go.
also refer to this answer that explains the memory structure of mifareclassic cards:
Reading Mifare Classic returns strange characters
If anyone came here figuring out how to read MifareClassic cards, then here is a repository that do so.
https://github.com/codes29/RFIDReader/blob/master/app/src/main/java/com/codes29/rfidreader/MainActivity.java
My app uses the foreground dispatch system to allow a user to tap their NFC tag in order to perform a read-then-write operation on the tag.
It works nicely if the user taps their tag properly (i.e., they tap it in the correct place on the phone and leave it connected for long enough), but if they physically remove the tag too early, then ndef.writeNdefMessage(...) throws an IOException.
That means the write operation fails, which is fair enough. But the real problem is that the same failed operation also deletes the entire ndef formatting/message from the tag!
My code is built around the snippets from the Advanced NFC | Android Developers page (as unfortunately the link to the ForegroundDispatch sample appears to be broken and there is no such sample project to import into Android Studio).
Step 1. Here is the logcat/stacktrace output when the user first taps their NFC tag, but moves it away too soon:
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. Next, the same user taps the same tag again but it appears to no longer contain an ndef message (which I have confirmed by altering the code and checking that 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)
I am getting this issue with both devices I have tested with so far - a Samsung Galaxy Core Prime (a lower end phone) running Android 5.1.1 and a Samsung Galaxy A5 (a mid range phone) running Android 5.0.2.
The NFC tags used by my app contain important information (i.e., inadvertently deleting the tag data is not an option!), so my questions are...
Why is my code (see below) erasing the tag data like this?
How can I fix the underlying problem, or is there an acceptable workaround?
Would it be worth me trying to use NfcA or IsoDep rather than Ndef?
Having done a lot of searching, I'm very surprised that this problem has not been discussed elsewhere, so if the problem isn't to do with my code, then could it be to do with the NFC tags I am using?...
The tags I'm using are NXP MIFARE Ultralight (Ultralight C) - NTAG203 (Tag type: ISO 14443-3A). Some of these I bought from ebay, and some I bought from Rapid NFC (a reputable company), yet I seem to have this problem with all of them.
Here is my complete code for the activity:
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 {
//makeToast("NFC available");
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("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} 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();
//makeToast("action: " + action);
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
throw new Exception("Tag was not ndef formatted: " + action); // line #124
}
else {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
ndef = Ndef.get(tag);
//makeToast("ndef: " + ndef);
if (ndef == null) {
throw new Exception("ndef == null!");
}
else {
// Connect
ndef.connect();
// Get cached message
NdefMessage ndefMessageOld = ndef.getCachedNdefMessage();
if (ndefMessageOld == null) {
throw new Exception("No ndef message on tag!");
}
else {
// Get old records
NdefRecord[] ndefRecordsOld = ndefMessageOld.getRecords();
int numRecords = (ndefRecordsOld == null) ? 0 : ndefRecordsOld.length;
// Create/copy 'new' records
NdefRecord[] ndefRecordsNew = new NdefRecord[numRecords];
for (int i = 0; i < numRecords; i++) {
ndefRecordsNew[i] = ndefRecordsOld[i];
}
// Create new message
NdefMessage ndefMessageNew = new NdefMessage(ndefRecordsNew);
// Write new message
ndef.writeNdefMessage(ndefMessageNew); // line #170
SUCCESS_COUNT++;
// Report success
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();
}
});
}
}
I really wonder what else you would expect to happen when you remove a storage device in the middle of overwriting its data.
Why is my code (see below) erasing the tag data like this?
You code is not really "erasing" data. It simply starts overwriting the data from the beginning of the tag memory leaving the tag in an undefined state when you interrupt writing.
An NFC tag only supports storing one NDEF message at a time. Consequently, when you start to write an new NDEF message, the old NDEF message needs to be overwritten. Thus,
ndef.writeNdefMessage(ndefMessageNew);
will overwrite the existing NDEF message starting at its first block. For NTAG203, MIFARE Ultralight and MIFARE Ultralight C (that's three different tag types by the way), this first block will be around block 4. writeNdefMessage will then write the new message block for block replacing old data with new data.
If the write procedure is interrupted (e.g. by pulling the tag from the reader field), then only parts of the new message are written (and parts of the old message may remain on the tag). Since neither the old nor the new message are complete, Android (just as any other NDEF reader) cannot read a valid NDEF message from the tag and, therefore, does not detect any NDEF message. The tag is still detected by your app since you also registered for the TECH_DISCOVERED intent (which does not require the tag to contain a vaild NDEF message).
How can I fix the underlying problem, or is there an acceptable workaround?
If your NDEF message is that long that your users are actually able to pull the tag while writing there is not much you can do against the pulling itself (except for instructing the users not to do so). NFC tags also do not have any form of pulling protection out-of-the-box. I.e. there are currently no tags that will reliably store the old NDEF message until the new NDEF message was written completely.
What you could possibly do is to store the old (or the new) NDEF message (possibly mapped to the tag ID) within your app and let the users restart the write procedure once it failed. Still, that would require user cooperation.
Would it be worth me trying to use NfcA or IsoDep rather than Ndef?
That might be another option: Don't use NDEF for the critical data but use an application-specific memory layout instead (or in addition to NDEF). NTAG/MIFARE Ultralight have a command set on top of ISO 14443-3A (NFC-A) and do not support ISO-DEP (ISO 14443-4). Thus, you could use NfcA (or MifareUltralight) to directly read from/write to the tags using low-level commands. You could structure the tag memory in two sections that you use to store the old and the new data:
Block x: Flag indicating which section (1 or 2) contains the valid data
Block x+1: First block of section 1
Block x+2: Second block of section 1
[...]
Block x+m: Last block of section 1
Block x+m+1: First block of section 2
Block x+m+2: Second block of section 2
[...]
Block x+2*m: Last block of section 2
Where x is the first block of your custom memory structure (you could even start that area after some fixed NDEF message) and m is the length of each section in blocks (1 block on NTAG/MF Ultralight has 4 bytes).
You would then use something like this to read and update your tag:
Read from block x to find out which section contains the vaild (newest) data -> section s.
Read the data from section s and use it as current data.
Write the new data to the other section (if s = 1: section 0; if s = 0: section 1).
If the data was written successfully (and completely), update block x with the new section number.
Low-level read and write commands look like this:
READ:
byte[] result = nfcA.transceive(new byte[] {
(byte)0x30, // READ
(byte)(blockNumber & 0x0ff)
});
WRITE:
byte[] result = nfcA.transceive(new byte[] {
(byte)0xA2, // WRITE
(byte)(blockNumber & 0x0ff),
byte0, byte1, byte2, byte3
});
I am just new to NFC in Android. I have a few questions on it. First, let me introduce the code. In my program, it just simply retrieves the payload from the records and log them as strings.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = NfcAdapter.getDefaultAdapter(this);
nfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
nfcFilter = new IntentFilter[]{
new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
};
techList = new String[][]{{Ndef.class.getName()}};
}
#Override
public void onResume(){
super.onResume();
if (adapter != null){
adapter.enableForegroundDispatch(this, nfcPendingIntent, nfcFilter, techList);
if (!adapter.isEnabled()){
Toast.makeText(this, "please enable your nfc", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(this, "your device do not have nfc.", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onPause(){
super.onPause();
if (adapter != null){
adapter.disableForegroundDispatch(this);
}
}
#Override
protected void onNewIntent(Intent intent){
String TAG = "onNewIntent";
super.onNewIntent(intent);
Log.d(TAG, "action: "+intent.getAction());
//Log.d(TAG, "type: "+intent.getType());
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction())){
Parcelable[] rawMsg = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsg != null){
for (int i=0; i<rawMsg.length; i++){
NdefMessage msg = (NdefMessage)rawMsg[i];
NdefRecord[] records = msg.getRecords();
for (int j=0; j<records.length; j++){
Log.d(TAG, records[j].toMimeType()+"");
byte [] payload = records[j].getPayload();
if (payload != null && payload.length > 0){
Log.d(TAG, new String(payload, 1, payload.length-1));
}
}
}
}
} else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction())){
} else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())){
Parcelable[] rawMsg = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
if (rawMsg != null){
for (int i=0; i<rawMsg.length; i++){
NdefMessage msg = (NdefMessage)rawMsg[i];
NdefRecord[] records = msg.getRecords();
for (int j=0; j<records.length; j++){
Log.d(TAG, records[j].toMimeType()+"");
byte [] payload = records[j].getPayload();
if (payload != null && payload.length > 0){
Log.d(TAG, new String(payload, 1, payload.length-1)+"("+j+")");
}
}
}
}
}
}
Here, it comes two questions:
The result tells the foreground dispatcher only catch TECH_DISCOVERED(with techList) and TAG_DISCOVERED(without techList) but miss the NDEF_DISCOVERED.
When I leave the app and scan the NFC tag, it automatically brings me to the website(I put a url as a record). How it tells this record consist an action to open a browser or make a call?
The NDEF_DISCOVERED intent filter will typically (some exceptions seem to exist) only match if it has a an associated data type that matches the NDEF message on the tag. So for instance, the data type specification */* will match any MIME type:
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {}
nfcFilter = new IntentFilter[]{
ndef,
new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED),
new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED)
};
Similarly, if you only want to trigger for a specific URL http://www.example.com/, you could use:
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addDataScheme("http");
ndef.addDataAuthority("www.example.com", null);
Note that -- with the foreground dispatch system -- you would typically only register the most generic intent filter that you want to match. Thus, if your foreground dispatch intent filter already contains the action TAG_DISCOVERED, there is no need to add any more specific filters (like TECH_DISCOVERED or NDEF_DISCOVERED), as your activity will already receive any discovered tag. The same applies for TECH_DISCOVERED in combination with Ndef tag technology: This already contains any tag that would trigger NDEF_DISCOVERED. However, note that the TAG_DISCOVERED intent filter is special in that it means "catch-all" when used with the foreground dispatch while it means "fallback-only" (i.e. only match if there is no better match with any other app) when used in manifest-based intent filters.
Via foreground NDEF pushing for API level 10 to 13, can I emulate between two non Android devices?
I have one Android and one Blackberry NFC enabled device. How can I do emulation between these devices in API level 10?
package com.app.app.nfctag;
import java.nio.charset.Charset;
import android.app.Activity;
import android.content.Intent;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.os.Parcelable;
import android.widget.Toast;
public class NFCDeveloper extends Activity{
NfcAdapter mNfcAdapter;
private NdefMessage pushMessage;
// TextView textView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, "NFC is not available", Toast.LENGTH_LONG).show();
finish();
return;
}
String text = ("Beam me up, Android!\n\n" + "Beam Time: " + System
.currentTimeMillis());
pushMessage = new NdefMessage(new NdefRecord[] { createMimeRecord(
"application/com.example.android.beam", text.getBytes())
});
}
/*public NdefMessage createNdefMessage(N event) {
String text = ("Beam me up, Android!\n\n" +
"Beam Time: " + System.currentTimeMillis());
NdefMessage msg = new NdefMessage(
new NdefRecord[] { createMimeRecord(
"application/com.example.android.beam", text.getBytes())
*//**
* The Android Application Record (AAR) is commented out. When a device
* receives a push with an AAR in it, the application specified in the AAR
* is guaranteed to run. The AAR overrides the tag dispatch system.
* You can add it back in to guarantee that this
* activity starts when receiving a beamed message. For now, this code
* uses the tag dispatch system.
*//*
//,NdefRecord.createApplicationRecord("com.example.android.beam")
});
return msg;
}
*/
public void onResume() {
super.onResume();
// Check to see that the Activity started due to an Android Beam
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
processIntent(getIntent());
}
mNfcAdapter.enableForegroundNdefPush(this, pushMessage);
}
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
void processIntent(Intent intent) {
// textView = (TextView) findViewById(R.id.textView);
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(
NfcAdapter.EXTRA_NDEF_MESSAGES);
// only one message sent during the beam
NdefMessage msg = (NdefMessage) rawMsgs[0];
// record 0 contains the MIME type, record 1 is the AAR, if present
// textView.setText(new String(msg.getRecords()[0].getPayload()));
}
public NdefRecord createMimeRecord(String mimeType, byte[] payload) {
byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII"));
NdefRecord mimeRecord = new NdefRecord(
NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload);
return mimeRecord;
}
}
NDEF is most common NFC standard which is utilized by most of other systems.
NDEF push works from Android device to Blackberry, just like to another Android device.
UPDATE:
You're right. It only works starting from API 14. Before that, Android uses the NPP protocol exclusively; SNEP (which BB supports) is only implemented from API 14.
I'm struggling with NFC and intents, I can discover tags, but how could I read info out from them?
As the code reveals: I get
Discovered tag with intent: Intent {
act= android.nfc.action TECH_DISCOVERED flg=0x13400000 cmp=com.example.android.apid/.nfc. Techfilter(has extra)}
I want to get info from the tag, should I be parsing the bytes etc?
Best Regs
h.
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.example.android.apis.nfc;
import java.util.List;
import com.example.android.apis.R;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentFilter.MalformedMimeTypeException;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.NfcF;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
/**
* An example of how to use the NFC foreground dispatch APIs. This will intercept any MIME data
* based NDEF dispatch as well as all dispatched for NfcF tags.
*/
public class ForegroundDispatch extends Activity {
//StringBuilder detailsRead = new StringBuilder();
private NfcAdapter mAdapter;
private PendingIntent mPendingIntent;
private IntentFilter[] mFilters;
private String[][] mTechLists;
private TextView mText;
private int mCount = 0;
static final String TAG = "TagReadActivity";
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.foreground_dispatch);
mText = (TextView) findViewById(R.id.text);
//resolveIntent(getIntent());
mText.setText("Scan a tag");
mAdapter = NfcAdapter.getDefaultAdapter(this);
// Create a generic PendingIntent that will be deliver to this activity. The NFC stack
// will fill in the intent with the details of the discovered tag before delivering to
// this activity.
mPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
// Setup an intent filter for all MIME based dispatches
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
try {
ndef.addDataType("*/*");
} catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
mFilters = new IntentFilter[] {
ndef,
};
// Setup a tech list for all NfcF tags
mTechLists = new String[][] { new String[] { NfcF.class.getName() } };
}
/**
void resolveIntent(Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
// When a tag is discovered we send it to the service to be save. We
// include a PendingIntent for the service to call back onto. This
// will cause this activity to be restarted with onNewIntent(). At
// that time we read it from the database and view it.
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
NdefMessage[] msgs;
if (rawMsgs != null) {
msgs = new NdefMessage[rawMsgs.length];
for (int i = 0; i < rawMsgs.length; i++) {
msgs[i] = (NdefMessage) rawMsgs[i];
}
} else {
// Unknown tag type
byte[] empty = new byte[] {};
NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
NdefMessage msg = new NdefMessage(new NdefRecord[] {record});
msgs = new NdefMessage[] {msg};
}
// Setup the views
} else {
Log.e(TAG, "Unknown intent " + intent);
mText.setText("My thing going on " + ++mCount + " with intent: " + action);
finish();
return;
}
}
**/
#Override
public void onResume() {
super.onResume();
mAdapter.enableForegroundDispatch(this, mPendingIntent, mFilters, mTechLists);
}
#Override
public void onNewIntent(Intent intent) {
Log.i("Foreground dispatch", "Discovered tag with intent: " + intent);
mText.setText("Discovered tag " + ++mCount + " with intent: " + intent);
}
#Override
public void onPause() {
super.onPause();
mAdapter.disableForegroundDispatch(this);
}
}
You should first call resolveIntent method.
You fire an intent for discovered that should be resolved.
Resolving it contains getting Tag data by calling getParcelableExtra() method on intent.
That will give you parcelable which you assign to a tag.
If you want code then post a comment here.
I have created an Android utility project which parses NDEF messages into higher-level objects. The same site includes abstract activity templates (and demo) for doing what you are looking to do (i.e. in foreground mode).
I've also created a graphical NDEF editor you might be intersted in.
Edit: Linked code is much improved
Once you get the tag, get the techlist and then based on tech call the tag method og the tech.
this value should be not null, if you would have taken appropriate tech from the tag.
once you get the tech it is easy to get the message from the tech classes provided.
on receiving the message parse the message to get the record.
cheers,
AA