Skip to content

Instantly share code, notes, and snippets.

@germanviscuso
Created January 11, 2012 16:09
Show Gist options
  • Save germanviscuso/1595378 to your computer and use it in GitHub Desktop.
Save germanviscuso/1595378 to your computer and use it in GitHub Desktop.
NFC utility classes for Android
package android.stickynotes;
import android.nfc.NdefRecord;
public interface NdefConverter {
public NdefRecord asNdef();
}
package com.androidstartup.moovi.util;
import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefRecord;
import android.os.Parcel;
import android.os.Parcelable;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* Represents a logical (unchunked) NDEF (NFC Data Exchange Format) record.
* <p>
* An NDEF record always contains:
* <ul>
* <li>3-bit TNF (Type Name Format) field: Indicates how to interpret the type
* field
* <li>Variable length type: Describes the record format
* <li>Variable length ID: A unique identifier for the record
* <li>Variable length payload: The actual data payload
* </ul>
* <p>
* The underlying record representation may be chunked across several NDEF
* records when the payload is large.
* <p>
* This is an immutable data class.
*/
public final class NdefRecordIcs implements Parcelable {
/**
* Indicates no type, id, or payload is associated with this NDEF Record.
* <p>
* Type, id and payload fields must all be empty to be a valid TNF_EMPTY
* record.
*/
public static final short TNF_EMPTY = 0x00;
/**
* Indicates the type field uses the RTD type name format.
* <p>
* Use this TNF with RTD types such as RTD_TEXT, RTD_URI.
*/
public static final short TNF_WELL_KNOWN = 0x01;
/**
* Indicates the type field contains a value that follows the media-type BNF
* construct defined by RFC 2046.
*/
public static final short TNF_MIME_MEDIA = 0x02;
/**
* Indicates the type field contains a value that follows the absolute-URI
* BNF construct defined by RFC 3986.
*/
public static final short TNF_ABSOLUTE_URI = 0x03;
/**
* Indicates the type field contains a value that follows the RTD external
* name specification.
* <p>
* Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
* Those are well known RTD constants, not external RTD constants.
*/
public static final short TNF_EXTERNAL_TYPE = 0x04;
/**
* Indicates the payload type is unknown.
* <p>
* This is similar to the "application/octet-stream" MIME type. The payload
* type is not explicitly encoded within the NDEF Message.
* <p>
* The type field must be empty to be a valid TNF_UNKNOWN record.
*/
public static final short TNF_UNKNOWN = 0x05;
/**
* Indicates the payload is an intermediate or final chunk of a chunked NDEF
* Record.
* <p>
* The payload type is specified in the first chunk, and subsequent chunks
* must use TNF_UNCHANGED with an empty type field. TNF_UNCHANGED must not
* be used in any other situation.
*/
public static final short TNF_UNCHANGED = 0x06;
/**
* Reserved TNF type.
* <p>
* The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat
* this value like TNF_UNKNOWN.
*
* @hide
*/
public static final short TNF_RESERVED = 0x07;
/**
* RTD Text type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_TEXT = { 0x54 }; // "T"
/**
* RTD URI type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_URI = { 0x55 }; // "U"
/**
* RTD Smart Poster type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_SMART_POSTER = { 0x53, 0x70 }; // "Sp"
/**
* RTD Alternative Carrier type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_ALTERNATIVE_CARRIER = { 0x61, 0x63 }; // "ac"
/**
* RTD Handover Carrier type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_HANDOVER_CARRIER = { 0x48, 0x63 }; // "Hc"
/**
* RTD Handover Request type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_HANDOVER_REQUEST = { 0x48, 0x72 }; // "Hr"
/**
* RTD Handover Select type. For use with TNF_WELL_KNOWN.
*/
public static final byte[] RTD_HANDOVER_SELECT = { 0x48, 0x73 }; // "Hs"
/**
* RTD Android app type. For use with TNF_EXTERNAL.
* <p>
* The payload of a record with type RTD_ANDROID_APP should be the package
* name identifying an application. Multiple RTD_ANDROID_APP records may be
* included in a single {@link NdefMessage}.
* <p>
* Use {@link #createApplicationRecord(String)} to create RTD_ANDROID_APP
* records.
*
* @hide
*/
public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
private static final byte FLAG_MB = (byte) 0x80;
private static final byte FLAG_ME = (byte) 0x40;
private static final byte FLAG_CF = (byte) 0x20;
private static final byte FLAG_SR = (byte) 0x10;
private static final byte FLAG_IL = (byte) 0x08;
/**
* NFC Forum "URI Record Type Definition"
*
* This is a mapping of "URI Identifier Codes" to URI string prefixes, per
* section 3.2.2 of the NFC Forum URI Record Type Definition document.
*/
private static final String[] URI_PREFIX_MAP = new String[] { "", // 0x00
"http://www.", // 0x01
"https://www.", // 0x02
"http://", // 0x03
"https://", // 0x04
"tel:", // 0x05
"mailto:", // 0x06
"ftp://anonymous:anonymous@", // 0x07
"ftp://ftp.", // 0x08
"ftps://", // 0x09
"sftp://", // 0x0A
"smb://", // 0x0B
"nfs://", // 0x0C
"ftp://", // 0x0D
"dav://", // 0x0E
"news:", // 0x0F
"telnet://", // 0x10
"imap:", // 0x11
"rtsp://", // 0x12
"urn:", // 0x13
"pop:", // 0x14
"sip:", // 0x15
"sips:", // 0x16
"tftp:", // 0x17
"btspp://", // 0x18
"btl2cap://", // 0x19
"btgoep://", // 0x1A
"tcpobex://", // 0x1B
"irdaobex://", // 0x1C
"file://", // 0x1D
"urn:epc:id:", // 0x1E
"urn:epc:tag:", // 0x1F
"urn:epc:pat:", // 0x20
"urn:epc:raw:", // 0x21
"urn:epc:", // 0x22
};
private final byte mFlags;
private final short mTnf;
private final byte[] mType;
private final byte[] mId;
private final byte[] mPayload;
/**
* Construct an NDEF Record.
* <p>
* Applications should not attempt to manually chunk NDEF Records - the
* implementation of android.nfc will automatically chunk an NDEF Record
* when necessary (and only present a single logical NDEF Record to the
* application). So applications should not use TNF_UNCHANGED.
*
* @param tnf
* a 3-bit TNF constant
* @param type
* byte array, containing zero to 255 bytes, must not be null
* @param id
* byte array, containing zero to 255 bytes, must not be null
* @param payload
* byte array, containing zero to (2 ** 32 - 1) bytes, must not
* be null
*/
public NdefRecordIcs(short tnf, byte[] type, byte[] id, byte[] payload) {
/*
* New NDEF records created by applications will have FLAG_MB|FLAG_ME
* set by default; when multiple records are stored in a {@link
* NdefMessage}, these flags will be corrected when the {@link
* NdefMessage} is serialized to bytes.
*/
this(tnf, type, id, payload, (byte) (FLAG_MB | FLAG_ME));
}
/**
* @hide
*/
/* package */NdefRecordIcs(short tnf, byte[] type, byte[] id,
byte[] payload, byte flags) {
/* check arguments */
if ((type == null) || (id == null) || (payload == null)) {
throw new IllegalArgumentException("Illegal null argument");
}
if (tnf < 0 || tnf > 0x07) {
throw new IllegalArgumentException("TNF out of range " + tnf);
}
/* Determine if it is a short record */
if (payload.length < 0xFF) {
flags |= FLAG_SR;
}
/* Determine if an id is present */
if (id.length != 0) {
flags |= FLAG_IL;
}
mFlags = flags;
mTnf = tnf;
mType = type.clone();
mId = id.clone();
mPayload = payload.clone();
}
/**
* Construct an NDEF Record from raw bytes.
* <p>
* Validation is performed to make sure the header is valid, and that the
* id, type and payload sizes appear to be valid.
*
* @throws FormatException
* if the data is not a valid NDEF record
*/
/*
* public NdefRecordIcs(byte[] data) throws FormatException { // Prevent
* compiler to complain about unassigned final fields mFlags = 0; mTnf = 0;
* mType = null; mId = null; mPayload = null; // Perform actual parsing if
* (parseNdefRecord(data) == -1) { throw new
* FormatException("Error while parsing NDEF record"); } }
*/
/**
* Returns the 3-bit TNF.
* <p>
* TNF is the top-level type.
*/
public short getTnf() {
return mTnf;
}
/**
* Returns the variable length Type field.
* <p>
* This should be used in conjunction with the TNF field to determine the
* payload format.
*/
public byte[] getType() {
return mType.clone();
}
/**
* Returns the variable length ID.
*/
public byte[] getId() {
return mId.clone();
}
/**
* Returns the variable length payload.
*/
public byte[] getPayload() {
return mPayload.clone();
}
/**
* Helper to return the NdefRecord as a URI. TODO: Consider making a member
* method instead of static TODO: Consider more validation that this is a
* URI record TODO: Make a public API
*
* @hide
*/
public static Uri parseWellKnownUriRecord(NdefRecordIcs record)
throws FormatException {
byte[] payload = record.getPayload();
if (payload.length < 2) {
throw new FormatException(
"Payload is not a valid URI (missing prefix)");
}
/*
* payload[0] contains the URI Identifier Code, per the NFC Forum
* "URI Record Type Definition" section 3.2.2.
*
* payload[1]...payload[payload.length - 1] contains the rest of the
* URI.
*/
int prefixIndex = (payload[0] & 0xff);
if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
throw new FormatException(
"Payload is not a valid URI (invalid prefix)");
}
String prefix = URI_PREFIX_MAP[prefixIndex];
byte[] fullUri = concat(prefix.getBytes(Charset.forName("UTF_8")),
Arrays.copyOfRange(payload, 1, payload.length));
return Uri.parse(new String(fullUri, Charset.forName("UTF_8")));
}
/**
* Creates an Android application NDEF record.
* <p>
* This record indicates to other Android devices the package that should be
* used to handle the rest of the NDEF message. You can embed this record
* anywhere into your NDEF message to ensure that the intended package
* receives the message.
* <p>
* When an Android device dispatches an {@link NdefMessage} containing one
* or more Android application records, the applications contained in those
* records will be the preferred target for the NDEF_DISCOVERED intent, in
* the order in which they appear in the {@link NdefMessage}. This dispatch
* behavior was first added to Android in Ice Cream Sandwich.
* <p>
* If none of the applications are installed on the device, a Market link
* will be opened to the first application.
* <p>
* Note that Android application records do not overrule applications that
* have called {@link NfcAdapter#enableForegroundDispatch}.
*
* @param packageName
* Android package name
* @return Android application NDEF record
*/
public static NdefRecord createApplicationRecord(String packageName) {
return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP,
new byte[] {},
packageName.getBytes(Charset.forName("US_ASCII")));
}
/**
* Creates an NDEF record of well known type URI.
*/
public static NdefRecord createUri(Uri uri) {
return createUri(uri.toString());
}
/**
* Creates an NDEF record of well known type URI.
*/
public static NdefRecord createUri(String uriString) {
byte prefix = 0x0;
for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
if (uriString.startsWith(URI_PREFIX_MAP[i])) {
prefix = (byte) i;
uriString = uriString.substring(URI_PREFIX_MAP[i].length());
break;
}
}
byte[] uriBytes = uriString.getBytes(Charset.forName("UTF_8"));
byte[] recordBytes = new byte[uriBytes.length + 1];
recordBytes[0] = prefix;
System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, new byte[0], recordBytes);
}
private static byte[] concat(byte[]... arrays) {
int length = 0;
for (byte[] array : arrays) {
length += array.length;
}
byte[] result = new byte[length];
int pos = 0;
for (byte[] array : arrays) {
System.arraycopy(array, 0, result, pos, array.length);
pos += array.length;
}
return result;
}
/**
* Returns this entire NDEF Record as a byte array.
*/
/*
* public byte[] toByteArray() { return generate(mFlags, mTnf, mType, mId,
* mPayload); }
*/
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mFlags);
dest.writeInt(mTnf);
dest.writeInt(mType.length);
dest.writeByteArray(mType);
dest.writeInt(mId.length);
dest.writeByteArray(mId);
dest.writeInt(mPayload.length);
dest.writeByteArray(mPayload);
}
public static final Parcelable.Creator<NdefRecordIcs> CREATOR = new Parcelable.Creator<NdefRecordIcs>() {
public NdefRecordIcs createFromParcel(Parcel in) {
byte flags = (byte) in.readInt();
short tnf = (short) in.readInt();
int typeLength = in.readInt();
byte[] type = new byte[typeLength];
in.readByteArray(type);
int idLength = in.readInt();
byte[] id = new byte[idLength];
in.readByteArray(id);
int payloadLength = in.readInt();
byte[] payload = new byte[payloadLength];
in.readByteArray(payload);
return new NdefRecordIcs(tnf, type, id, payload, flags);
}
public NdefRecordIcs[] newArray(int size) {
return new NdefRecordIcs[size];
}
};
// private native int parseNdefRecord(byte[] data);
// private native byte[] generate(short flags, short tnf, byte[] type,
// byte[] id, byte[] data);
}
package android.stickynotes;
import java.io.IOException;
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.nfc.tech.NdefFormatable;
import android.os.Build;
import android.os.Parcelable;
import android.util.Log;
import android.widget.Toast;
import android.os.PatternMatcher;
import android.stickynotes.NdefConverter;
public class NfcUtil {
private static final String TAG = NfcUtil.class.getSimpleName();
public static String URI_TYPE = "http";
public static String URI_HOST = "myhost";
public static String URI_PREFIX = "mypath";
public static String URI_ID_FIELD = "id";
public static String BASE_NDEF_URI;
private Activity activity;
private NfcAdapter nfcAdapter;
private PendingIntent nfcPendingIntent;
private IntentFilter[] ndefReadFilters;
private IntentFilter[] writeFilters;
private boolean mWriteMode = false;
public NfcUtil(Activity activity) {
this.activity = activity;
nfcAdapter = getNfcAdapter();
nfcPendingIntent = getPendingIntent();
ndefReadFilters = getReadFiltersForText(); //getReadFiltersForUrl();
writeFilters = getWriteFilters();
}
public IntentFilter[] getReadFiltersForUrl() {
BASE_NDEF_URI = URI_TYPE + "://" + URI_HOST + URI_PREFIX + "?"
+ URI_ID_FIELD + "=";
// Intent filters for reading specifically formatted urls from a tag or exchanging over p2p.
IntentFilter ndefDetected = new IntentFilter(
NfcAdapter.ACTION_NDEF_DISCOVERED);
ndefDetected.addDataScheme(URI_TYPE);
ndefDetected.addDataAuthority(URI_HOST, null);
ndefDetected.addDataPath(URI_PREFIX, PatternMatcher.PATTERN_PREFIX);
return new IntentFilter[] { ndefDetected };
}
public IntentFilter[] getReadFiltersForText() {
// Intent filters for reading text from a tag or exchanging over p2p.
IntentFilter ndefDetected = new IntentFilter(
NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndefDetected.addDataType("text/plain"); //"*/*");
} catch (MalformedMimeTypeException e) { }
return new IntentFilter[] { ndefDetected };
}
public IntentFilter[] getWriteFilters(){
IntentFilter tagDetected = new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED);
return new IntentFilter[] { tagDetected };
}
public PendingIntent getPendingIntent(){
return PendingIntent.getActivity(activity, 0, new Intent(
activity, activity.getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
}
public NfcAdapter getNfcAdapter() {
if (hasNfcSupport()) {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
return nfcAdapter;
}
return null;
}
public boolean isNfcEnabled() {
NfcAdapter adapter = getNfcAdapter();
return adapter != null && adapter.isEnabled();
}
public boolean checkNdefIntentMatch(Intent intent) {
return NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction());
}
public boolean checkTagIntentMatch(Intent intent) {
return NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction());
}
// Pre: Intent must match ACTION_NDEF_DISCOVERED
public byte[] getNdefPayload(Intent intent) {
NdefMessage[] messages = getNdefMessages(intent);
byte[] payload = messages[0].getRecords()[0].getPayload();
return payload;
}
NdefMessage[] getNdefMessages(Intent intent) {
// Parse the intent
NdefMessage[] msgs = null;
Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
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 };
}
return msgs;
}
public Tag getTag(Intent intent) {
return intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
}
public void enableNdefExchangeMode(NdefMessage msg) {
enableForegroundNdefPush(msg);
enableForegroundDispatch();
}
public void enableEnhancedNdefExchangeMode(NdefConverter entity, String pckgName) {
nfcAdapter.enableForegroundNdefPush(activity,
getIcsEnhancedNdefMessage(entity.asNdef(), pckgName));
enableForegroundDispatch();
}
public void disableNdefExchangeMode() {
disableForegroundNdefPush();
disableForegroundDispatch();
}
public void enableTagWriteMode() {
mWriteMode = true;
enableForegroundDispatch(writeFilters);
}
public void disableTagWriteMode() {
mWriteMode = false;
disableForegroundDispatch();
}
public boolean isWriteMode(){
return mWriteMode;
}
public boolean writeTag(NdefRecord record, Tag tag) {
NdefMessage message = new NdefMessage(new NdefRecord[] { record });
return writeTag(message, tag);
}
public boolean writeTag(NdefMessage message, Tag tag) {
int size = message.toByteArray().length;
try {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
if (!ndef.isWritable()) {
shortToast("Tag is read-only.");
return false;
}
if (ndef.getMaxSize() < size) {
shortToast("Tag capacity is " + ndef.getMaxSize()
+ " bytes, message is " + size + " bytes.");
return false;
}
ndef.writeNdefMessage(message);
shortToast("Wrote message to pre-formatted tag.");
return true;
} else {
NdefFormatable format = NdefFormatable.get(tag);
if (format != null) {
try {
format.connect();
format.format(message);
shortToast("Formatted tag and wrote message");
return true;
} catch (IOException e) {
shortToast("Failed to format tag.");
return false;
}
} else {
shortToast("Tag doesn't support NDEF.");
return false;
}
}
} catch (Exception e) {
shortToast("Failed to write tag");
}
return false;
}
public static NdefRecord getIcsAppRecord(String pckgName) {
return NdefRecordIcs.createApplicationRecord(pckgName);
}
public NdefMessage getIcsEnhancedNdefMessage(NdefRecord firstRecord,
String pckgName) {
return new NdefMessage(new NdefRecord[] { firstRecord,
getIcsAppRecord(pckgName) });
}
public static NdefRecord getByUri(String uriString) {
return NdefRecordIcs.createUri(uriString);
}
public void disableForegroundNdefPush(Activity activity) {
nfcAdapter.disableForegroundNdefPush(activity);
}
public void disableForegroundNdefPush() {
disableForegroundNdefPush(activity);
}
public void enableForegroundNdefPush(Activity activity, NdefMessage msg) {
nfcAdapter.enableForegroundNdefPush(activity, msg);
}
public void enableForegroundNdefPush(NdefMessage msg) {
enableForegroundNdefPush(activity, msg);
}
public void disableForegroundDispatch(Activity activity) {
nfcAdapter.disableForegroundDispatch(activity);
}
public void disableForegroundDispatch() {
disableForegroundDispatch(activity);
}
public void enableForegroundDispatch(Activity activity,
PendingIntent intent, IntentFilter[] filters, String[][] techLists) {
nfcAdapter.enableForegroundDispatch(activity, intent, filters,
techLists);
}
public void enableForegroundDispatch(Activity activity,
PendingIntent intent, IntentFilter[] filters) {
enableForegroundDispatch(activity, intent, filters, null);
}
public void enableForegroundDispatch(PendingIntent intent,
IntentFilter[] filters) {
enableForegroundDispatch(activity, intent, filters, null);
}
public void enableForegroundDispatch(PendingIntent intent) {
enableForegroundDispatch(activity, intent, ndefReadFilters, null);
}
public void enableForegroundDispatch() {
enableForegroundDispatch(activity, nfcPendingIntent, ndefReadFilters, null);
}
public void enableForegroundDispatch(IntentFilter[] filters) {
enableForegroundDispatch(activity, nfcPendingIntent, filters, null);
}
public static boolean hasNfcSupport() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1;
}
public void shortToast(String text) {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
}
public void longToast(String text) {
Toast.makeText(activity, text, Toast.LENGTH_LONG).show();
}
public void requestNfcActivation(){
if (!isNfcEnabled()) {
longToast("Please activate NFC");
activity.startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment