Communication between an Android device and a NFC chip
Hi there,
In this post I’ll talk about how to read a NFC tag with your Android devices.
Requirements
- Android device with a NFC chip.
- Android 2.3.3 (API 10) or higher.
- A MiFare Classic 1k Tag
Manifest permission
In order to start developing you first need to tell the application that it requires the NFC chip of your device. We can accomplish this by adding the following rule to the Manifest.xml file.
<uses-permission android:name="android.permission.NFC" />
And of course we need to tell Google Play that it can only be installed on devices with an actual NFC chip, by doing this we avoid that users without a NFC chip can install the application on their device.
So add the following rule to the Manifest.xml file.
<uses-feature android:name="android.hardware.NFC" android:required="true" />
At this point we’re done with the permissions in the Manifest.xml, but don’t close the file just yet.
Intent-filters
In order to let our application respond to a tag that gets held against our device, we need to register ourselves to some intent filters.
At this moment there are three intent filters available:
- ACTION_NDEF_DISCOVERD
- ACTION_TECH_DISCOVERD
- ACTION_TAG_DISCOVERD
There is a difference between these three, but that is not in the scope of this post, but the important thing is that the Android framework priorities these intent filters. As you can see in the following picture.
These need to be added at the activity that you want to start after the device recognizes that a NFC tag is pressed against it. Check the manual of your NFC tag for the right intent filter.
We can do this by adding the following lines to out Manifest.xml file and in the specific activity block.
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED" />
<meta-data
android:name="android.nfc.action.TECH_DISCOVERED"
android:resource="@xml/nfc_tech_filter" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
</intent-filter>
As you can see the only intent filter that needs more attention is the ACTION_TECH_DISCOVED. This is because it relies on a tech filter file to know what NFC technologies your application supports. This file is placed in the xml directory of the resources, if this directory doesn’t exist you simply need to create one. And after that create a file that’s called nfc_tech_filter.xml.
This is an example of the tech filter:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<tech-list>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.MifareClassic</tech>
</tech-list>
</resources>
For the complete list of supported tech filters go to the tech filters of the Google Developers site.
At this point we can finally go to our MainActivity.java, where all of the fun happens.
Java-coding
First we need the NfcAdapter, you can use this by importing the android.nfc.NfcAdapter. After this we declare and create an instance of the NfcAdapter. Also create declare and create an instance of a PendingIntent, we need this later in our code.
Private NfcAdapter myNfcAdapter;
Private PendingIntent myPendingIntent;
And in your OnCreate function
myNfcAdapter = NfcAdapter.getDefaultAdapter(this);
myPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP), 0);
Now we have an instance of the NfcAdapter called myNfcAdapter and an instance of a PendingIntent called myPendingIntent. What the myPendingIntent does is it start a function in it’s own Activity when it’s called.
We can achieve this by calling two function in our code, that is the enableForegroundDispatch() and disableForegroundDispatch(), you do this in you onResume() and onPause() function.
@Override
protected void onResume() {
super.onResume();
if (myNfcAdapter != null) {
myNfcAdapter.enableForegroundDispatch(this, myPendingIntent, null, null);
}
}
@Override
protected void onPause() {
super.onPause();
if (myNfcAdapter != null) {
myNfcAdapter.disableForegroundDispatch(this);
}
}
As you can see in the onResume() function we use the myPendingIntent as an parameter of the enableForegroundDispatch() function of the myNfcAdapter. What the enableForegroundDispatch() does, is instead of creating a new Activity it actually fires the myPendingIntent what redirects to itself.
After this create a function called onNewIntent() with the parameter intent of the type Intent;
@Override
protected void onNewIntent(Intent intent)
{
}
This function get called as the myPendingIntent gets fired in the onResume() function. And in this function is where all the magic happens.
Place where the magic happens
In this function we open a connection to a NFC tag and read data of the tag.
First start by checking if the NFC tag is of the right intent filter;
if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction()))
{
}
Otherwise you end up with the wrong tag, because NDEF and TECH requires two different methods of reading the tag.
In this post I focus on the ACTION_TECH_DISCOVERED, because I don’t have a NDEF tag laying around, so I cannot test the code.
First we need to save the tag to a variable of the type Tag:
Tag tag = intent.getParceableExtra(NfcAdapter.EXTRA_TAG);
*You probably need to import the Tag library (android.nfc.Tag).
As you can see we get our tag from the intent, because the intent filtered out the other tags. Because my tag is a Mifare Classis tag we need to convert it.
MifareClassis mfc = MifareClassic.get(tag);
*You probably need to import the MifareClassic library (android.nfc.tech.MifareClassic).
After this you need to authenticate with your key to the sector on the tag, otherwise you cannot write or read to the blocks on the card.
boolean auth = false;
auth = mfc.authenticateSectorWithKeyA(sectorNumber, MifareClassic.KEY_DEFAULT);
After this you just loop to the sectors and blocks and store the data in a variable of the type byte[] which is an array.
Here is the full code of the onNewIntent() function.
@Override
protected void onNewIntent(Intent intent)
{
if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
MifareClassic mfc = MifareClassic.get(tag);
byte[] data;
try{
mfc.connect();
boolean auth = false;
String cardData = null;
int secCount = mfc.getSectorCount();
int bCount = 0;
int bIndex = 0;
for (int j = 0; j < secCount; j++)
{
auth = mfc.authenticateSectorWithKeyA(j, MifareClassic.KEY_DEFAULT);
if (auth)
{
bCount = mfc.getBlockCountInSector(j);
bIndex = 0;
for (int i = 0; i < bCount; i++)
{
bIndex = mfc.sectorToBlock(j);
data = mfc.readBlock(bIndex);
bIndex++;
}
}
else
{
Toast.makeText(this, "NFC authentication failed", Toast.LENGTH_SHORT).show();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
After this your application should run and you can read your data in the data variable and do all kind of stuff with it.
Extra reading and sources
http://www.creativebloq.com/android/getting-started-nfc-android-5122811
http://code.tutsplus.com/tutorials/reading-nfc-tags-with-android–mobile-17278
http://developer.android.com/guide/topics/connectivity/nfc/nfc.html
http://developer.android.com/reference/android/nfc/NfcAdapter.html
http://www.developer.com/ws/android/nfc-programming-in-android.html