The above footage from our NFC Workshop walks Android developers through code which demonstrates how to write content to an NFC tag.
You can follow along below or download the source here.
We start by coming into our 'onCreate()' and getting the default NFC adapter.
We want to create our pending intent as a 'FLAG_ACTIVITY_SINGLE_TOP' so that as we tap it, we aren't creating multiple instances of the same application.
Notice that there are three different intent filters:
- 'ACTION_TAG_DISCOVERED',
- 'ACTION_NDEF_DISCOVERED'
- 'ACTION_TECH_DISCOVERED'.
The only one we are using in our array is 'ACTION_TAG_DISCOVERED' because right now we are just trying to find a tag.
public class MainActivity extends Activity {
private static final String TAG = "NFCWriteTag";
private NfcAdapter mNfcAdapter;
private IntentFilter[] mWriteTagFilters;
private PendingIntent mNfcPendingIntent;
private boolean silent=false;
private boolean writeProtect = false;
private Context context;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = getApplicationContext();
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
mNfcPendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP), 0);
IntentFilter discovery=new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
IntentFilter ndefDetected = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
IntentFilter techDetected = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);
// Intent filters for writing to a tag
mWriteTagFilters = new IntentFilter[] { discovery };
}
Now we come down to 'onResume()' where we get the intent. We check the NFC adapter, and if it's not enabled, we put a dialog box to take us to our settings to enable it. Otherwise it fails.
If NFC is enabled, we execute 'enableForegroundDispatch()', and pass in our pending intent and filters for what kind of tags we want. Now, in the foreground, with our activity running, we will bypass the OS dispatch system.
(Note: We can do this from the Manifest to launch our application from the OS level)
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
@Override
protected void onResume() {
super.onResume();
if(mNfcAdapter != null) {
if (!mNfcAdapter.isEnabled()){
LayoutInflater inflater = getLayoutInflater();
View dialoglayout = inflater.inflate(R.layout.nfc_settings_layout,(ViewGroup) findViewById(R.id.nfc_settings_layout));
new AlertDialog.Builder(this).setView(dialoglayout)
.setPositiveButton("Update Settings", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface arg0, int arg1) {
Intent setnfc = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
startActivity(setnfc);
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish(); // exit application if user cancels
}
}).create().show();
}
mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, mWriteTagFilters, null);
} else {
Toast.makeText(context, "Sorry, No NFC Adapter found.", Toast.LENGTH_SHORT).show();
}
}
At 'onPause()' we disable foreground dispatch.
'onNewIntent()' is the actual intent once we've tapped the tag. We check to see if the action in the intent matches 'ACTION_TAG_DISCOVERED', and if it is, this is how we get the tag from the reader.
'Tag' is an NFC object, and we can use 'getParceableExtra()' to get its data. We grab the list of technologies associated with that tag using 'getTechList()' to see if it has the technologies we are trying to support.
In this case, we are checking to see if it is writeable. If it is, we pass in what we are going to write to the tag, and then we pass in the physical tag to write it.
@Override
protected void onPause() {
super.onPause();
if(mNfcAdapter != null) mNfcAdapter.disableForegroundDispatch(this);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) {
// validate that this tag can be written
Tag detectedTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if(supportedTechs(detectedTag.getTechList())) {
// check if tag is writable (to the extent that we can
if(writableTag(detectedTag)) {
//writeTag here
WriteResponse wr = writeTag(getTagAsNdef(), detectedTag);
String message = (wr.getStatus() == 1? "Success: " : "Failed: ") + wr.getMessage();
Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context,"This tag is not writable",Toast.LENGTH_SHORT).show();
Sounds.PlayFailed(context, silent);
}
} else {
Toast.makeText(context,"This tag type is not supported",Toast.LENGTH_SHORT).show();
Sounds.PlayFailed(context, silent);
}
}
}
In 'writeTag()', we pass in our NDEF message and find its size. Then we try to grab the formatted tag. If the tag is NDEF encoded and formatted, this will come back true and we can connect to it.
Once we've connected to the tag, we check to see if the tag is writeable. If it is, we can check how much space it has to see if what we are going to write will fit.
If the tag has room, we can write our message on it. If we try to write too much, it will fail as an exception in the try/catch statement.
public WriteResponse writeTag(NdefMessage message, Tag tag) {
int size = message.toByteArray().length;
String mess = "";
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
return new WriteResponse(0,"Tag is read-only");
}
if (ndef.getMaxSize() < size) {
mess = "Tag capacity is " + ndef.getMaxSize() + " bytes, message is " + size
+ " bytes.";
return new WriteResponse(0,mess);
}
ndef.writeNdefMessage(message);
if(writeProtect) ndef.makeReadOnly();
mess = "Wrote message to pre-formatted tag.";
return new WriteResponse(1,mess);
} else {
NdefFormatable format = NdefFormatable.get(tag);
if (format != null) {
try {
format.connect();
format.format(message);
mess = "Formatted tag and wrote message";
return new WriteResponse(1,mess);
} catch (IOException e) {
mess = "Failed to format tag.";
return new WriteResponse(0,mess);
}
} else {
mess = "Tag doesn't support NDEF.";
return new WriteResponse(0,mess);
}
}
} catch (Exception e) {
mess = "Failed to write tag";
return new WriteResponse(0,mess);
}
}
private class WriteResponse {
int status;
String message;
WriteResponse(int Status, String Message) {
this.status = Status;
this.message = Message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
}
public static boolean supportedTechs(String[] techs) {
boolean ultralight=false;
boolean nfcA=false;
boolean ndef=false;
for(String tech:techs) {
if(tech.equals("android.nfc.tech.MifareUltralight")) {
ultralight=true;
}else if(tech.equals("android.nfc.tech.NfcA")) {
nfcA=true;
} else if(tech.equals("android.nfc.tech.Ndef") || tech.equals("android.nfc.tech.NdefFormatable")) {
ndef=true;
}
}
if(ultralight && nfcA && ndef) {
return true;
} else {
return false;
}
}
private boolean writableTag(Tag tag) {
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
Toast.makeText(context,"Tag is read-only.",Toast.LENGTH_SHORT).show();
Sounds.PlayFailed(context, silent);
ndef.close();
return false;
}
ndef.close();
return true;
}
} catch (Exception e) {
Toast.makeText(context,"Failed to read tag",Toast.LENGTH_SHORT).show();
Sounds.PlayFailed(context, silent);
}
return false;
}
Here we specify what we are going to write to the tag ('smartwhere.com/nfc.html') and get its bytes and create its payload. Then we specify the prefix, which is specified by the first character of the payload. This particular one will put 'http://www' in front of the payload.
Now we create a new NDEF record and wrap it into a message. In addition to the NDEF record, we also pass the message an AAR. If 'addAAR' is enabled, it will launch the specified application and deliver our URI. Otherwise we just return the URI record.
An Android application record (AAR) allows us to circumvent the normal dispatch system and deliver content tag content into a specific application. And if the specified application doesn't exist, Android takes you to the marketplace to get it.
private NdefMessage getTagAsNdef() {
boolean addAAR = false;
String uniqueId = "smartwhere.com/nfc.html";
byte[] uriField = uniqueId.getBytes(Charset.forName("US-ASCII"));
byte[] payload = new byte[uriField.length + 1]; //add 1 for the URI Prefix
payload[0] = 0x01; //prefixes http://www. to the URI
System.arraycopy(uriField, 0, payload, 1, uriField.length); //appends URI to payload
NdefRecord rtdUriRecord = new NdefRecord(
NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, new byte[0], payload);
if(addAAR) {
// note: returns AAR for different app (nfcreadtag)
return new NdefMessage(new NdefRecord[] {
rtdUriRecord, NdefRecord.createApplicationRecord("com.tapwise.nfcreadtag")
});
} else {
return new NdefMessage(new NdefRecord[] {
rtdUriRecord});
}
}
}
Video: NFC Workshop, July 17, 2012
Location: Seattle Waterfront
Post by: Kelsey W
great!, thanks for the effort and time!!
ReplyDeleteI was using your sample to write some of my tags. One of them was MIFARE Classic. Does Android support writing them? Your application does not support them for sure but why?
ReplyDeleteHello ,I am also getting error while writing in MIFARE card.
DeleteDid you get any solution?
This example doesn't have manifest or res layouts, It's a great work and is good for me, but maybe not for people that is begining with nft stufs. But thnks in my name! ;)
ReplyDeleteThis blog site is pretty good! How was it made . I view something genuinely interesting about your site so I saved to my bookmarks . You can visit my Exit intent technology
ReplyDeleteI can't build nfc_settings_layout. Where is nfc_settings_layout? Please help me!
ReplyDeleteThanks for sharing information.Here is the information of IDENTIS that manufactures RFID tags:
ReplyDeleteNFC Tag