package com.oreilly.demo.android.pa.sensordemo; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Locale; import android.app.Activity; import android.app.PendingIntent; import android.content.Intent; 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.Message; import android.os.Parcelable; import android.view.View; import android.view.View.OnClickListener; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class NFC233 extends Activity { private static enum NFCType { UNKNOWN, TEXT, URI, SMART_POSTER, ABSOLUTE_URI } private final Handler enableForegroundDispatchHandler = new Handler() { public void handleMessage(Message msg) { enableForegroundDispatch(); } }; private final Handler enableForegroundPushHandler = new Handler() { public void handleMessage(Message msg) { enableForegroundPush(); } }; private final Handler writeTagHandler = new Handler() { public void handleMessage(Message msg) { writeTag(); } }; private final Handler mgsToaster = new Handler() { public void handleMessage(Message msg) { toastMessage(msg.obj); } }; private Tag mytag; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.nfc233); setTitle("Near Field Communication - 2.3.3"); setupView(); } @Override protected void onPause() { super.onPause(); try { if(NfcAdapter.getDefaultAdapter(this) != null) { NfcAdapter.getDefaultAdapter(this).disableForegroundDispatch(this); NfcAdapter.getDefaultAdapter(this).disableForegroundNdefPush(this); } } catch (Exception t) { t.printStackTrace(); } } private void setupView() { findViewById(R.id.close).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { finish(); } }); // if what launched this was caused by ACTION_NDEF_DISCOVERED or // ACTION_TECH_DISCOVERED then get rid of the buttons and analyze the // intent. try { if(getIntent() != null && getIntent().getAction() != null && (getIntent().getAction().equals(NfcAdapter.ACTION_NDEF_DISCOVERED) || getIntent().getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED))) { findViewById(R.id.enablefdispatch).setVisibility(View.GONE); findViewById(R.id.enablefpush).setVisibility(View.GONE); findViewById(R.id.tagtype).setVisibility(View.VISIBLE); findViewById(R.id.tagid).setVisibility(View.VISIBLE); findViewById(R.id.tagdata).setVisibility(View.VISIBLE); findViewById(R.id.what).setVisibility(View.VISIBLE); findViewById(R.id.tagwrite).setVisibility(View.VISIBLE); mytag = getTag(getIntent()); analyzeIntent(getIntent()); } else if(NfcAdapter.getDefaultAdapter(this) == null || !NfcAdapter.getDefaultAdapter(this).isEnabled()) { findViewById(R.id.enablefdispatch).setVisibility(View.GONE); findViewById(R.id.enablefpush).setVisibility(View.GONE); findViewById(R.id.tagtype).setVisibility(View.GONE); findViewById(R.id.tagdata).setVisibility(View.GONE); ((TextView) findViewById(R.id.tagid)).setText("NFC not enabled!"); } else { findViewById(R.id.tagtype).setVisibility(View.GONE); findViewById(R.id.tagid).setVisibility(View.GONE); findViewById(R.id.tagdata).setVisibility(View.GONE); findViewById(R.id.enablefdispatch).setVisibility(View.VISIBLE); findViewById(R.id.enablefdispatch).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { enableForegroundDispatchHandler.sendEmptyMessage(0); } }); findViewById(R.id.enablefpush).setVisibility(View.VISIBLE); findViewById(R.id.enablefpush).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { enableForegroundPushHandler.sendEmptyMessage(0); } }); } findViewById(R.id.tagwrite).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { writeTagHandler.sendEmptyMessage(0); } }); } catch (Exception t) { ((TextView) findViewById(R.id.tagid)).setText("ERROR: "+t.toString()); } } private void enableForegroundDispatch() { findViewById(R.id.enablefdispatch).setVisibility(View.GONE); Toast.makeText(getBaseContext(), "Foreground Dispatch Enabled! Please scan a tag", Toast.LENGTH_SHORT).show(); PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0); // we are going set up to receive as a ACTION_TAG_DISCOVERED intent NfcAdapter.getDefaultAdapter(this).enableForegroundDispatch(this, intent, null, null); } @Override public void onNewIntent(Intent intent) { findViewById(R.id.tagtype).setVisibility(View.VISIBLE); findViewById(R.id.tagid).setVisibility(View.VISIBLE); findViewById(R.id.tagdata).setVisibility(View.VISIBLE); findViewById(R.id.what).setVisibility(View.VISIBLE); findViewById(R.id.tagwrite).setVisibility(View.VISIBLE); mytag = getTag(intent); analyzeIntent(intent); } private void enableForegroundPush() { findViewById(R.id.enablefpush).setVisibility(View.GONE); Toast.makeText(getBaseContext(), "Foreground Push Enabled! Please tap another NFC endabled Android phone", Toast.LENGTH_SHORT).show(); NdefRecord[] rec = new NdefRecord[1]; rec[0] = newTextRecord("NFC Foreground Push Message"); NdefMessage msg = new NdefMessage(rec); NfcAdapter.getDefaultAdapter(this).enableForegroundNdefPush(this, msg); } private void writeTag() { if(mytag == null) { Toast.makeText(this, "No tag available to write to!", Toast.LENGTH_SHORT).show(); return; } final Ndef ndefref = Ndef.get(mytag); if(ndefref == null) { Toast.makeText(this, "Tag is not Ndef: NULL", Toast.LENGTH_SHORT).show(); return; } if(!ndefref.isWritable()) { Toast.makeText(this, "The tag is not writable!", Toast.LENGTH_SHORT).show(); return; } EditText what = (EditText) findViewById(R.id.what); if(what == null || what.getText() == null || what.getText().toString() == null || what.getText().toString().trim().length() < 1) { Toast.makeText(this, "Please input some text to write to the tag.", Toast.LENGTH_SHORT).show(); return; } String msgstr = what.getText().toString().trim(); NdefRecord[] rec = new NdefRecord[1]; rec[0] = newTextRecord(msgstr); final NdefMessage msg = new NdefMessage(rec); (new Thread() { public void run() { try { Message.obtain(mgsToaster, 0, "Tag writing attempt started").sendToTarget(); int count = 0; if(!ndefref.isConnected()) { ndefref.connect(); } while(!ndefref.isConnected()) { if(count > 6000) { throw new Exception("Unable to connect to tag"); } count++; sleep(10); } ndefref.writeNdefMessage(msg); Message.obtain(mgsToaster, 0, "Tag write successful!").sendToTarget(); } catch (Exception t) { t.printStackTrace(); Message.obtain(mgsToaster, 0, "Tag writing failed! - "+t.getMessage()).sendToTarget(); } finally { // ignore close failure... try { ndefref.close(); } catch (IOException e) { } } } }).start(); } private NdefRecord newTextRecord(String text) { byte[] langBytes = Locale.ENGLISH.getLanguage().getBytes(Charset.forName("US-ASCII")); byte[] textBytes = text.getBytes(Charset.forName("UTF-8")); char status = (char) (langBytes.length); byte[] data = new byte[1 + langBytes.length + textBytes.length]; data[0] = (byte) status; System.arraycopy(langBytes, 0, data, 1, langBytes.length); System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length); return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); } private Tag getTag(final Intent intent) { if(intent == null || !intent.hasExtra(NfcAdapter.EXTRA_TAG)) return null; Parcelable tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); if(tag == null) return null; return (Tag) tag; } private void toastMessage(Object ob) { String msg = ""; if(ob == null || !(ob instanceof String)) { msg = "-- Attempt to toast msg failed --"; } else { msg = (String) ob; } Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } // From here on down is the same code as in com.oreilly.demo.android.pa.sensordemo.NFC private void analyzeIntent(final Intent intent) { if(intent == null) return; String id = getTagId(intent); NFCType type = NFCType.UNKNOWN; String datastr = null; byte[] data = null; NdefMessage tag = getTagData(intent); if(tag != null) { type = getTagType(tag); if(type != NFCType.UNKNOWN) { datastr = getTagData(tag); } else data = getTagRawData(tag); } if(datastr != null) updateViewInfo(id, type, datastr); else updateViewInfo(id, type, data); } private void updateViewInfo(String id, NFCType type, byte[] data) { updateViewInfo(id, type, data != null ? getHexString(data) : null); } private void updateViewInfo(String id, NFCType type, String data) { if(id != null) { ((TextView) findViewById(R.id.tagid)).setText("TagID: "+id); } if(type != NFCType.UNKNOWN) { String typestr = ""; switch(type) { case TEXT: typestr = "Text"; break; case SMART_POSTER: typestr = "Smart Poster"; break; case URI: typestr = "URI"; break; case ABSOLUTE_URI: typestr = "URI (Abs)"; break; default: typestr = "UNKNOWN"; break; } ((TextView) findViewById(R.id.tagtype)).setText("TagType: "+typestr); } if(data != null) { ((TextView) findViewById(R.id.tagdata)).setText("TagData:\n"+data); } } private String getTagId(final Intent intent) { if(intent == null) return null; byte[] byte_id = intent.getByteArrayExtra(NfcAdapter.EXTRA_ID); if(byte_id == null) return null; return getHexString(byte_id); } private NdefMessage getTagData(final Intent intent) { if(intent == null || !intent.hasExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) return null; Parcelable[] msgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); if(msgs == null || msgs.length < 1) { return null; } NdefMessage[] nmsgs = new NdefMessage[msgs.length]; for(int i=0;i<msgs.length;i++) { nmsgs[i] = (NdefMessage) msgs[i]; } // we will only grab the first msg as we are handling only 1 tag at the moment return nmsgs[0]; } private NFCType getTagType(final NdefMessage msg) { if(msg == null) return null; // we are only grabbing the first recognizable item for (NdefRecord record : msg.getRecords()) { if(record.getTnf() == NdefRecord.TNF_WELL_KNOWN) { if(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) { return NFCType.TEXT; } if(Arrays.equals(record.getType(), NdefRecord.RTD_URI)) { return NFCType.URI; } if(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)) { return NFCType.SMART_POSTER; } } else if(record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) { return NFCType.ABSOLUTE_URI; } } return null; } private String getTagData(final NdefMessage msg) { if(msg == null) return null; // we are only grabbing the first recognizable item for(NdefRecord record : msg.getRecords()) { if(record.getTnf() == NdefRecord.TNF_WELL_KNOWN) { if(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) { return getText(record.getPayload()); } if(Arrays.equals(record.getType(), NdefRecord.RTD_URI)) { return getURI(record.getPayload()); } if(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)) { if(record.getPayload() == null || record.getPayload().length < 1) return null; try { NdefMessage subrecords = new NdefMessage(record.getPayload()); return getSubRecordData(subrecords.getRecords()); } catch (Exception e) { e.printStackTrace(); return null; } } } else if(record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) { return getAbsoluteURI(record.getPayload()); } } return null; } private String getSubRecordData(final NdefRecord[] records) { if(records == null || records.length < 1) return null; String data = ""; for(NdefRecord record : records) { if(record.getTnf() == NdefRecord.TNF_WELL_KNOWN) { if(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)) { data += getText(record.getPayload()) + "\n"; } if(Arrays.equals(record.getType(), NdefRecord.RTD_URI)) { data += getURI(record.getPayload()) + "\n"; } else { data += "OTHER KNOWN DATA\n"; } } else if(record.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) { data += getAbsoluteURI(record.getPayload()) + "\n"; } else data += "OTHER UNKNOW DATA\n"; } return data; } private byte[] getTagRawData(final NdefMessage msg) { if(msg == null || msg.getRecords().length < 1) return null; // we are only grabbing the first item return msg.getRecords()[0].getPayload(); } /* * the First Byte of the payload contains the "Status Byte Encodings" field, per the NFC Forum "Text Record Type Definition" section 3.2.1. * * Bit_7 is the Text Encoding Field. * * if Bit_7 == 0 the the text is encoded in UTF-8 else if Bit_7 == 1 then the text is encoded in UTF16 * Bit_6 is currently always 0 (reserved for future use) * Bits 5 to 0 are the length of the IANA language code. */ private String getText(final byte[] payload) { if(payload == null) return null; try { String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16"; int languageCodeLength = payload[0] & 0077; return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); } catch (Exception e) { e.printStackTrace(); } return null; } private String getAbsoluteURI(final byte[] payload) { if(payload == null) return null; return new String(payload, Charset.forName("UTF-8")); } /** * the First Byte of the payload contains the prefix byte */ private String getURI(final byte[] payload) { if(payload == null || payload.length < 1) return null; String prefix = convertUriPrefix(payload[0]); return (prefix != null ? prefix : "" ) + new String(Arrays.copyOfRange(payload, 1,payload.length)); } /** * NFC Forum "URI Record Type Definition" * * Conversion of prefix based on section 3.2.2 of the NFC Forum URI Record Type Definition document. */ private String convertUriPrefix(final byte prefix) { if(prefix == (byte) 0x00) return ""; else if(prefix == (byte) 0x01) return "http://www."; else if(prefix == (byte) 0x02) return "https://www."; else if(prefix == (byte) 0x03) return "http://"; else if(prefix == (byte) 0x04) return "https://"; else if(prefix == (byte) 0x05) return "tel:"; else if(prefix == (byte) 0x06) return "mailto:"; else if(prefix == (byte) 0x07) return "ftp://anonymous:anonymous@"; else if(prefix == (byte) 0x08) return "ftp://ftp."; else if(prefix == (byte) 0x09) return "ftps://"; else if(prefix == (byte) 0x0A) return "sftp://"; else if(prefix == (byte) 0x0B) return "smb://"; else if(prefix == (byte) 0x0C) return "nfs://"; else if(prefix == (byte) 0x0D) return "ftp://"; else if(prefix == (byte) 0x0E) return "dav://"; else if(prefix == (byte) 0x0F) return "news:"; else if(prefix == (byte) 0x10) return "telnet://"; else if(prefix == (byte) 0x11) return "imap:"; else if(prefix == (byte) 0x12) return "rtsp://"; else if(prefix == (byte) 0x13) return "urn:"; else if(prefix == (byte) 0x14) return "pop:"; else if(prefix == (byte) 0x15) return "sip:"; else if(prefix == (byte) 0x16) return "sips:"; else if(prefix == (byte) 0x17) return "tftp:"; else if(prefix == (byte) 0x18) return "btspp://"; else if(prefix == (byte) 0x19) return "btl2cap://"; else if(prefix == (byte) 0x1A) return "btgoep://"; else if(prefix == (byte) 0x1B) return "tcpobex://"; else if(prefix == (byte) 0x1C) return "irdaobex://"; else if(prefix == (byte) 0x1D) return "file://"; else if(prefix == (byte) 0x1E) return "urn:epc:id:"; else if(prefix == (byte) 0x1F) return "urn:epc:tag:"; else if(prefix == (byte) 0x20) return "urn:epc:pat:"; else if(prefix == (byte) 0x21) return "urn:epc:raw:"; else if(prefix == (byte) 0x22) return "urn:epc:"; else if(prefix == (byte) 0x23) return "urn:nfc:"; return null; } private final static char[] HEX = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7','8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; // convert bytes to a hex string private static String getHexString(final byte[] bytes) { StringBuffer hex = new StringBuffer(bytes.length * 2); for (int i = 0; i < bytes.length; i++) { for (int j = 1; j >= 0; j--) { hex.append(HEX[(bytes[i] >> (j * 4)) & 0xF]); } } return hex.toString(); } }