diff --git a/api/15.txt b/api/15.txt index ddf5baf30ff9d..687a4ce7529f6 100644 --- a/api/15.txt +++ b/api/15.txt @@ -12659,7 +12659,6 @@ package android.nfc { method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]); method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); - method public static deprecated android.nfc.NfcAdapter getDefaultAdapter(); method public boolean isEnabled(); method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...); method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...); diff --git a/api/current.txt b/api/current.txt index b2f20c72a577e..db3b30d618b59 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12602,10 +12602,12 @@ package android.nfc { public class FormatException extends java.lang.Exception { ctor public FormatException(); ctor public FormatException(java.lang.String); + ctor public FormatException(java.lang.String, java.lang.Throwable); } public final class NdefMessage implements android.os.Parcelable { ctor public NdefMessage(byte[]) throws android.nfc.FormatException; + ctor public NdefMessage(android.nfc.NdefRecord, android.nfc.NdefRecord...); ctor public NdefMessage(android.nfc.NdefRecord[]); method public int describeContents(); method public android.nfc.NdefRecord[] getRecords(); @@ -12616,8 +12618,10 @@ package android.nfc { public final class NdefRecord implements android.os.Parcelable { ctor public NdefRecord(short, byte[], byte[], byte[]); - ctor public NdefRecord(byte[]) throws android.nfc.FormatException; + ctor public deprecated NdefRecord(byte[]) throws android.nfc.FormatException; method public static android.nfc.NdefRecord createApplicationRecord(java.lang.String); + method public static android.nfc.NdefRecord createExternal(java.lang.String, java.lang.String, byte[]); + method public static android.nfc.NdefRecord createMime(java.lang.String, byte[]); method public static android.nfc.NdefRecord createUri(android.net.Uri); method public static android.nfc.NdefRecord createUri(java.lang.String); method public int describeContents(); @@ -12625,7 +12629,7 @@ package android.nfc { method public byte[] getPayload(); method public short getTnf(); method public byte[] getType(); - method public byte[] toByteArray(); + method public deprecated byte[] toByteArray(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final byte[] RTD_ALTERNATIVE_CARRIER; @@ -12650,7 +12654,6 @@ package android.nfc { method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], java.lang.String[][]); method public deprecated void enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); - method public static deprecated android.nfc.NfcAdapter getDefaultAdapter(); method public boolean isEnabled(); method public boolean isNdefPushEnabled(); method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...); diff --git a/core/java/android/nfc/FormatException.java b/core/java/android/nfc/FormatException.java index 7045a0312bdb0..a57de1e0e21a0 100644 --- a/core/java/android/nfc/FormatException.java +++ b/core/java/android/nfc/FormatException.java @@ -24,4 +24,8 @@ public class FormatException extends Exception { public FormatException(String message) { super(message); } + + public FormatException(String message, Throwable e) { + super(message, e); + } } diff --git a/core/java/android/nfc/NdefMessage.java b/core/java/android/nfc/NdefMessage.java index c79fabf5b054a..38bc16d6c223f 100644 --- a/core/java/android/nfc/NdefMessage.java +++ b/core/java/android/nfc/NdefMessage.java @@ -16,90 +16,170 @@ package android.nfc; +import java.nio.ByteBuffer; +import java.util.Arrays; + import android.os.Parcel; import android.os.Parcelable; + /** - * Represents an NDEF (NFC Data Exchange Format) data message that contains one or more {@link - * NdefRecord}s. - *
An NDEF message includes "records" that can contain different sets of data, such as - * MIME-type media, a URI, or one of the supported RTD types (see {@link NdefRecord}). An NDEF - * message always contains zero or more NDEF records.
- *This is an immutable data class. + * Represents an immutable NDEF Message. + *
+ * NDEF (NFC Data Exchange Format) is a light-weight binary format, + * used to encapsulate typed data. It is specified by the NFC Forum, + * for transmission and storage with NFC, however it is transport agnostic. + *
+ * NDEF defines messages and records. An NDEF Record contains + * typed data, such as MIME-type media, a URI, or a custom + * application payload. An NDEF Message is a container for + * one or more NDEF Records. + *
+ * When an Android device receives an NDEF Message + * (for example by reading an NFC tag) it processes it through + * a dispatch mechanism to determine an activity to launch. + * The type of the first record in the message has + * special importance for message dispatch, so design this record + * carefully. + *
+ * Use {@link #NdefMessage(byte[])} to construct an NDEF Message from + * binary data, or {@link #NdefMessage(NdefRecord[])} to + * construct from one or more {@link NdefRecord}s. + *
+ * {@link NdefMessage} and {@link NdefRecord} implementations are + * always available, even on Android devices that do not have NFC hardware. + *
+ * {@link NdefRecord}s are intended to be immutable (and thread-safe), + * however they may contain mutable fields. So take care not to modify + * mutable fields passed into constructors, or modify mutable fields + * obtained by getter methods, unless such modification is explicitly + * marked as safe. + * + * @see NfcAdapter#ACTION_NDEF_DISCOVERED + * @see NdefRecord */ public final class NdefMessage implements Parcelable { - private static final byte FLAG_MB = (byte) 0x80; - private static final byte FLAG_ME = (byte) 0x40; - private final NdefRecord[] mRecords; /** - * Create an NDEF message from raw bytes. - *
- * Validation is performed to make sure the Record format headers are valid, - * and the ID + TYPE + PAYLOAD fields are of the correct size. - * @throws FormatException + * Construct an NDEF Message by parsing raw bytes.
+ * Strict validation of the NDEF binary structure is performed: + * there must be at least one record, every record flag must + * be correct, and the total length of the message must match + * the length of the input data.
+ * This parser can handle chunked records, and converts them + * into logical {@link NdefRecord}s within the message.
+ * Once the input data has been parsed to one or more logical + * records, basic validation of the tnf, type, id, and payload fields + * of each record is performed, as per the documentation on + * on {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}
+ * If either strict validation of the binary format fails, or + * basic validation during record construction fails, a + * {@link FormatException} is thrown
+ * Deep inspection of the type, id and payload fields of + * each record is not performed, so it is possible to parse input + * that has a valid binary format and confirms to the basic + * validation requirements of + * {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}, + * but fails more strict requirements as specified by the + * NFC Forum. + * + *
+ * It is safe to re-use the data byte array after construction: + * this constructor will make an internal copy of all necessary fields. + * + * @param data raw bytes to parse + * @throws FormatException if the data cannot be parsed */ public NdefMessage(byte[] data) throws FormatException { - mRecords = null; // stop compiler complaints about final field - if (parseNdefMessage(data) == -1) { - throw new FormatException("Error while parsing NDEF message"); + if (data == null) { + throw new NullPointerException("null data"); + } + ByteBuffer buffer = ByteBuffer.wrap(data); + + mRecords = NdefRecord.parse(buffer, false); + + if (buffer.remaining() > 0) { + throw new FormatException("trailing data"); } } /** - * Create an NDEF message from NDEF records. + * Construct an NDEF Message from one or more NDEF Records. + * + * @param record first record (mandatory) + * @param records additional records (optional) + */ + public NdefMessage(NdefRecord record, NdefRecord ... records) { + // validate + if (record == null) { + throw new NullPointerException("record cannot be null"); + } + for (NdefRecord r : records) { + if (r == null) { + throw new NullPointerException("record cannot be null"); + } + } + + mRecords = new NdefRecord[1 + records.length]; + mRecords[0] = record; + System.arraycopy(records, 0, mRecords, 1, records.length); + } + + /** + * Construct an NDEF Message from one or more NDEF Records. + * + * @param records one or more records */ public NdefMessage(NdefRecord[] records) { - mRecords = new NdefRecord[records.length]; - System.arraycopy(records, 0, mRecords, 0, records.length); - } - - /** - * Get the NDEF records inside this NDEF message. - * - * @return array of zero or more NDEF records. - */ - public NdefRecord[] getRecords() { - return mRecords.clone(); - } - - /** - * Returns a byte array representation of this entire NDEF message. - */ - public byte[] toByteArray() { - //TODO: allocate the byte array once, copy each record once - //TODO: process MB and ME flags outside loop - if ((mRecords == null) || (mRecords.length == 0)) - return new byte[0]; - - byte[] msg = {}; - - for (int i = 0; i < mRecords.length; i++) { - byte[] record = mRecords[i].toByteArray(); - byte[] tmp = new byte[msg.length + record.length]; - - /* Make sure the Message Begin flag is set only for the first record */ - if (i == 0) { - record[0] |= FLAG_MB; - } else { - record[0] &= ~FLAG_MB; + // validate + if (records.length < 1) { + throw new IllegalArgumentException("must have at least one record"); + } + for (NdefRecord r : records) { + if (r == null) { + throw new NullPointerException("records cannot contain null"); } - - /* Make sure the Message End flag is set only for the last record */ - if (i == (mRecords.length - 1)) { - record[0] |= FLAG_ME; - } else { - record[0] &= ~FLAG_ME; - } - - System.arraycopy(msg, 0, tmp, 0, msg.length); - System.arraycopy(record, 0, tmp, msg.length, record.length); - - msg = tmp; } - return msg; + mRecords = records; + } + + /** + * Get the NDEF Records inside this NDEF Message.
+ * An NDEF Message always has one or more NDEF Records. + * + * @return array of one or more NDEF records. + */ + public NdefRecord[] getRecords() { + return mRecords; + } + + /** + * Return this NDEF MEssage as raw bytes.
+ * The NDEF Message is formatted as per the NDEF 1.0 specification, + * and the byte array is suitable for network transmission or storage + * in an NFC Forum NDEF compatible tag.
+ * This method will not chunk any records, and will always use the
+ * short record (SR) format and omit the identifier field when possible.
+ *
+ * @return NDEF Message in binary format
+ */
+ public byte[] toByteArray() {
+ int length = 0;
+ for (NdefRecord r : mRecords) {
+ length += r.getByteLength();
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(length);
+
+ for (int i=0; i
+ * NDEF (NFC Data Exchange Format) is a light-weight binary format,
+ * used to encapsulate typed data. It is specified by the NFC Forum,
+ * for transmission and storage with NFC, however it is transport agnostic.
+ *
+ * NDEF defines messages and records. An NDEF Record contains
+ * typed data, such as MIME-type media, a URI, or a custom
+ * application payload. An NDEF Message is a container for
+ * one or more NDEF Records.
+ *
+ * This class represents logical (complete) NDEF Records, and can not be
+ * used to represent chunked (partial) NDEF Records. However
+ * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
+ * containing chunked records, and will return a message with unchunked
+ * (complete) records.
+ *
+ * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
+ * that provides high level typing for the rest of the record. The
+ * remaining fields are variable length and not always present:
* The underlying record
- * representation may be chunked across several NDEF records when the payload is
- * large.
- * This is an immutable data class.
+ *
+ * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
+ * and {@link NdefRecord#createExternal} are included to create well-formatted
+ * NDEF Records with correctly set tnf, type, id and payload fields, please
+ * use these helpers whenever possible.
+ *
+ * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
+ * if you know what you are doing and what to set the fields individually.
+ * Only basic validation is performed with this constructor, so it is possible
+ * to create records that do not confirm to the strict NFC Forum
+ * specifications.
+ *
+ * The binary representation of an NDEF Record includes additional flags to
+ * indicate location with an NDEF message, provide support for chunking of
+ * NDEF records, and to pack optional fields. This class does not expose
+ * those details. To write an NDEF Record as binary you must first put it
+ * into an @{link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
+ *
+ * {@link NdefMessage} and {@link NdefRecord} implementations are
+ * always available, even on Android devices that do not have NFC hardware.
+ *
+ * {@link NdefRecord}s are intended to be immutable (and thread-safe),
+ * however they may contain mutable fields. So take care not to modify
+ * mutable fields passed into constructors, or modify mutable fields
+ * obtained by getter methods, unless such modification is explicitly
+ * marked as safe.
+ *
+ * @see NfcAdapter#ACTION_NDEF_DISCOVERED
+ * @see NdefMessage
*/
public final class NdefRecord implements Parcelable {
/**
- * Indicates no type, id, or payload is associated with this NDEF Record.
- *
- * Type, id and payload fields must all be empty to be a valid TNF_EMPTY
- * record.
+ * Indicates the record is empty.
+ * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
*/
public static final short TNF_EMPTY = 0x00;
/**
- * Indicates the type field uses the RTD type name format.
+ * Indicates the type field contains a well-known RTD type name.
+ * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
*
- * Use this TNF with RTD types such as RTD_TEXT, RTD_URI.
+ * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
+ *
+ * @see #RTD_URI
+ * @see #RTD_TEXT
+ * @see #RTD_SMART_POSTER
+ * @see #createUri
*/
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.
+ * Indicates the type field contains a media-type BNF
+ * construct, defined by RFC 2046.
+ * Use this with MIME type names such as {@literal "image/jpeg"}, or
+ * using the helper {@link #createMime}.
+ *
+ * @see #createMime
*/
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.
+ * Indicates the type field contains an absolute-URI
+ * BNF construct defined by RFC 3986.
+ * When creating new records prefer {@link #createUri},
+ * since it offers more compact URI encoding
+ * ({@literal #RTD_URI} allows compression of common URI prefixes).
+ *
+ * @see #createUri
*/
public static final short TNF_ABSOLUTE_URI = 0x03;
/**
- * Indicates the type field contains a value that follows the RTD external
- * name specification.
+ * Indicates the type field contains an external type name.
+ * Used to encode custom payloads. When creating new records
+ * use the helper {@link #createExternal}.
+ * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.
*
* Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
* Those are well known RTD constants, not external RTD constants.
+ *
+ * @see #createExternal
*/
public static final short TNF_EXTERNAL_TYPE = 0x04;
/**
- * Indicates the payload type is unknown.
+ * Indicates the payload type is unknown.
+ * NFC Forum explains this should be treated similarly to the
+ * "application/octet-stream" MIME type. The payload
+ * type is not explicitly encoded within the record.
*
- * This is similar to the "application/octet-stream" MIME type. The payload
- * type is not explicitly encoded within the NDEF Message.
- *
- * The type field must be empty to be a valid TNF_UNKNOWN record.
+ * The type field is empty in an {@literal TNF_UNKNOWN} record.
*/
public static final short TNF_UNKNOWN = 0x05;
/**
* Indicates the payload is an intermediate or final chunk of a chunked
- * NDEF Record.
- *
- * 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.
+ * NDEF Record.
+ * {@literal TNF_UNCHANGED} can not be used with this class
+ * since all {@link NdefRecord}s are already unchunked, however they
+ * may appear in the binary format.
*/
public static final short TNF_UNCHANGED = 0x06;
@@ -106,42 +165,49 @@ public final class NdefRecord implements Parcelable {
public static final short TNF_RESERVED = 0x07;
/**
- * RTD Text type. For use with TNF_WELL_KNOWN.
+ * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_TEXT = {0x54}; // "T"
/**
- * RTD URI type. For use with TNF_WELL_KNOWN.
+ * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_URI = {0x55}; // "U"
/**
- * RTD Smart Poster type. For use with TNF_WELL_KNOWN.
+ * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
/**
- * RTD Alternative Carrier type. For use with TNF_WELL_KNOWN.
+ * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
/**
- * RTD Handover Carrier type. For use with TNF_WELL_KNOWN.
+ * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
/**
- * RTD Handover Request type. For use with TNF_WELL_KNOWN.
+ * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
/**
- * RTD Handover Select type. For use with TNF_WELL_KNOWN.
+ * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
+ * @see #TNF_WELL_KNOWN
*/
public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
/**
- * RTD Android app type. For use with TNF_EXTERNAL.
+ * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
*
* The payload of a record with type RTD_ANDROID_APP
* should be the package name identifying an application.
@@ -161,8 +227,7 @@ public final class NdefRecord implements Parcelable {
private static final byte FLAG_IL = (byte) 0x08;
/**
- * NFC Forum "URI Record Type Definition"
- *
+ * 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.
*/
@@ -204,84 +269,247 @@ public final class NdefRecord implements Parcelable {
"urn:epc:", // 0x22
};
- private final byte mFlags;
+ private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
+
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
private final short mTnf;
private final byte[] mType;
private final byte[] mId;
private final byte[] mPayload;
/**
- * Construct an NDEF Record.
+ * Create a new Android Application Record (AAR).
*
- * 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.
+ * This record indicates to other Android devices the package
+ * that should be used to handle the entire NDEF message.
+ * You can embed this record anywhere into your message
+ * to ensure that the intended package receives the message.
+ *
+ * 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 {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
+ * intent, in the order in which they appear in the message.
+ * This dispatch behavior was first added to Android in
+ * Ice Cream Sandwich.
+ *
+ * If none of the applications have a are installed on the device,
+ * a Market link will be opened to the first application.
+ *
+ * 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) {
+ if (packageName.length() == 0) {
+ throw new IllegalArgumentException("empty package name");
+ }
+ return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
+ packageName.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Create a new NDEF Record containing a URI.
+ * Use this method to encode a URI (or URL) into an NDEF Record.
+ * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
+ * and {@link #RTD_URI}. This is the most efficient encoding
+ * of a URI into NDEF.
+ * Reference specification: NFCForum-TS-RTD_URI_1.0
+ *
+ * @param uri URI to encode.
+ * @return an NDEF Record containing the URI
+ * @throws IllegalArugmentException if a valid record cannot be created
+ */
+ public static NdefRecord createUri(Uri uri) {
+ return createUri(uri.toString());
+ }
+
+ /**
+ * Create a new NDEF Record containing a URI.
+ * Use this method to encode a URI (or URL) into an NDEF Record.
+ * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
+ * and {@link #RTD_URI}. This is the most efficient encoding
+ * of a URI into NDEF.
+ * Reference specification: NFCForum-TS-RTD_URI_1.0
+ *
+ * @param uriString string URI to encode.
+ * @return an NDEF Record containing the URI
+ * @throws IllegalArugmentException if a valid record cannot be created
+ */
+ public static NdefRecord createUri(String uriString) {
+ if (uriString.length() == 0) {
+ throw new IllegalArgumentException("empty uriString");
+ }
+
+ byte prefix = 0;
+ 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(Charsets.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, null, recordBytes);
+ }
+
+ /**
+ * Create a new NDEF Record containing MIME data.
+ * Use this method to encode MIME-typed data into an NDEF Record,
+ * such as "text/plain", or "image/jpeg".
+ * Expects US-ASCII characters in mimeType. The encoding of the
+ * mimeData depends on the mimeType.
+ * For efficiency, This method might not make an internal copy of the
+ * mimeData byte array, so take care not
+ * to re-use the mimeData byte array while still using the returned
+ * NdefRecord.
+ *
+ * @param mimeType MIME type, expects US-ASCII characters only
+ * @param mimeData MIME data as bytes
+ * @return an NDEF Record containing the MIME-typed data
+ * @throws IllegalArugmentException if a valid record cannot be created
+ */
+ public static NdefRecord createMime(String mimeType, byte[] mimeData) {
+ if (mimeType.length() == 0) {
+ throw new IllegalArgumentException("empty mimeType");
+ }
+
+ return new NdefRecord(TNF_MIME_MEDIA, mimeType.getBytes(Charsets.US_ASCII), null,
+ mimeData);
+ }
+
+ /**
+ * Create a new NDEF Record containing external (application-specific) data.
+ * Use this method to encode application specific data into an NDEF Record.
+ * The data is typed by a domain name (usually your Android package name) and
+ * a domain-specific type. This data is packaged into a "NFC Forum External
+ * Type" NDEF Record.
+ * Both the domain and type used to construct an external record are case
+ * insensitive, and this implementation will encode all characters to lower
+ * case. Only a subset of ASCII characters are allowed for the domain
+ * and type. There are no restrictions on the payload data.
+ * For efficiency, This method might not make an internal copy of the
+ * data byte array, so take care not
+ * to re-use the data byte array while still using the returned
+ * NdefRecord.
+ *
+ * Reference specification: NFCForum-TS-RTD_1.0
+ * @param domain domain-name of issuing organization
+ * @param type domain-specific type of data
+ * @param data payload as bytes
+ * @throws IllegalArugmentException if a valid record cannot be created
+ */
+ public static NdefRecord createExternal(String domain, String type, byte[] data) {
+ if (domain.length() == 0 || type.length() == 0) {
+ throw new IllegalArgumentException("empty domain or type");
+ }
+ byte[] byteDomain = domain.getBytes(Charsets.US_ASCII);
+ ensureValidDomain(byteDomain);
+ toLowerCase(byteDomain);
+ byte[] byteType = type.getBytes(Charsets.US_ASCII);
+ ensureValidWkt(byteType);
+ toLowerCase(byteType);
+
+ byte[] b = new byte[byteDomain.length + 1 + byteType.length];
+ System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
+ b[byteDomain.length] = ':';
+ System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
+
+ return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
+ }
+
+ /**
+ * Construct an NDEF Record from its component fields.
+ * Recommend to use helpers such as {#createUri} or
+ * {{@link #createExternal} where possible, since they perform
+ * stricter validation that the record is correctly formatted
+ * as per NDEF specifications. However if you know what you are
+ * doing then this constructor offers the most flexibility.
+ * An {@link NdefRecord} represents a logical (complete)
+ * record, and cannot represent NDEF Record chunks.
+ * Basic validation of the tnf, type, id and payload is performed
+ * as per the following rules:
+ *
+ * If any of the above validation
+ * steps fail then {@link IllegalArgumentException} is thrown.
+ * Deep inspection of the type, id and payload fields is not
+ * performed, so it is possible to create NDEF Records
+ * that conform to section 3.2.6
+ * but fail other more strict NDEF specification requirements. For
+ * example, the payload may be invalid given the tnf and type.
+ *
+ * To omit a type, id or payload field, set the parameter to an
+ * empty byte array or null.
*
* @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 type byte array, containing zero to 255 bytes, or null
+ * @param id byte array, containing zero to 255 bytes, or null
* @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
- * must not be null
+ * or null
+ * @throws IllegalArugmentException if a valid record cannot be created
*/
public NdefRecord(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));
- }
+ /* convert nulls */
+ if (type == null) type = EMPTY_BYTE_ARRAY;
+ if (id == null) id = EMPTY_BYTE_ARRAY;
+ if (payload == null) payload = EMPTY_BYTE_ARRAY;
- /**
- * @hide
- */
- /*package*/ NdefRecord(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");
+ String message = validateTnf(tnf, type, id, payload);
+ if (message != null) {
+ throw new IllegalArgumentException(message);
}
- 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();
+ mType = type;
+ mId = id;
+ mPayload = payload;
}
/**
- * Construct an NDEF Record from raw bytes.
- *
- * Validation is performed to make sure the header is valid, and that
- * the id, type and payload sizes appear to be valid.
+ * Construct an NDEF Record from raw bytes.
+ * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
+ * instead. This is because it does not make sense to parse a record:
+ * the NDEF binary format is only defined for a message, and the
+ * record flags MB and ME do not make sense outside of the context of
+ * an entire message.
+ * This implementation will attempt to parse a single record by ignoring
+ * the MB and ME flags, and otherwise following the rules of
+ * {@link NdefMessage#NdefMessage(byte[])}.
*
- * @throws FormatException if the data is not a valid NDEF record
+ * @param data raw bytes to parse
+ * @throws FormatException if the data cannot be parsed into a valid record
+ * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
*/
+ @Deprecated
public NdefRecord(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");
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ NdefRecord[] rs = parse(buffer, true);
+
+ if (buffer.remaining() > 0) {
+ throw new FormatException("data too long");
}
+
+ mTnf = rs[0].mTnf;
+ mType = rs[0].mType;
+ mId = rs[0].mId;
+ mPayload = rs[0].mPayload;
}
/**
@@ -298,6 +526,9 @@ public final class NdefRecord implements Parcelable {
*
* This should be used in conjunction with the TNF field to determine the
* payload format.
+ *
+ * Returns an empty byte array if this record
+ * does not have a type field.
*/
public byte[] getType() {
return mType.clone();
@@ -305,6 +536,9 @@ public final class NdefRecord implements Parcelable {
/**
* Returns the variable length ID.
+ *
+ * Returns an empty byte array if this record
+ * does not have an id field.
*/
public byte[] getId() {
return mId.clone();
@@ -312,11 +546,33 @@ public final class NdefRecord implements Parcelable {
/**
* Returns the variable length payload.
+ *
+ * Returns an empty byte array if this record
+ * does not have a payload field.
*/
public byte[] getPayload() {
return mPayload.clone();
}
+ /**
+ * Return this NDEF Record as a byte array.
+ * This method is deprecated, use {@link NdefMessage#toByteArray}
+ * instead. This is because the NDEF binary format is not defined for
+ * a record outside of the context of a message: the MB and ME flags
+ * cannot be set without knowing the location inside a message.
+ * This implementation will attempt to serialize a single record by
+ * always setting the MB and ME flags (in other words, assume this
+ * is a single-record NDEF Message).
+ *
+ * @deprecated use {@link NdefMessage#toByteArray()} instead
+ */
+ @Deprecated
+ public byte[] toByteArray() {
+ ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
+ writeToByteBuffer(buffer, true, true);
+ return buffer.array();
+ }
+
/**
* Helper to return the NdefRecord as a URI.
* TODO: Consider making a member method instead of static
@@ -347,63 +603,6 @@ public final class NdefRecord implements Parcelable {
return Uri.parse(new String(fullUri, Charsets.UTF_8));
}
- /**
- * Creates an Android application NDEF record.
- *
- * 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.
- *
- * 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.
- *
- * If none of the applications are installed on the device,
- * a Market link will be opened to the first application.
- *
- * 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(Charsets.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(Charsets.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) {
@@ -419,18 +618,215 @@ public final class NdefRecord implements Parcelable {
}
/**
- * Returns this entire NDEF Record as a byte array.
+ * Main parsing method.
+ * Expects NdefMessage to begin immediately, allows trailing data.
+ * Currently has strict validation of all fields as per NDEF 1.0
+ * specification section 2.5. We will attempt to keep this as strict as
+ * possible to encourage well-formatted NDEF.
+ * Always returns 1 or more NdefRecord's, or throws FormatException.
+ *
+ * @param buffer ByteBuffer to read from
+ * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
+ * @return one or more records
+ * @throws FormatException on any parsing error
*/
- public byte[] toByteArray() {
- return generate(mFlags, mTnf, mType, mId, mPayload);
+ static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
+ List
+ * Validates the requirements of NFCForum-TS-NDEF_1.0 section
+ * 3.2.6 (Type Name Format). This just validates that the tnf
+ * is valid, and that the relevant type, id and payload
+ * fields are present (or empty) for this tnf. It does not
+ * perform any deep inspection of the type, id and payload fields.
+ * Also does not allow TNF_UNCHANGED since this class is only used
+ * to present logical (unchunked) records.
+ *
+ * @return null if valid, or a string error if invalid.
+ */
+ static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
+ switch (tnf) {
+ case TNF_EMPTY:
+ if (type.length != 0 || id.length != 0 || payload.length != 0) {
+ return "unexpected data in TNF_EMPTY record";
+ }
+ return null;
+ case TNF_WELL_KNOWN:
+ case TNF_MIME_MEDIA:
+ case TNF_ABSOLUTE_URI:
+ case TNF_EXTERNAL_TYPE:
+ return null;
+ case TNF_UNKNOWN:
+ case TNF_RESERVED:
+ if (type.length != 0) {
+ return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
+ }
+ return null;
+ case TNF_UNCHANGED:
+ return "unexpected TNF_UNCHANGED in first chunk or logical record";
+ default:
+ return String.format("unexpected tnf value: 0x%02x", tnf);
+ }
+ }
+
+ /**
+ * Serialize record for network transmission.
+ * Uses specified MB and ME flags.
+ * Does not chunk records.
+ */
+ void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
+ boolean sr = mPayload.length < 256;
+ boolean il = mId.length > 0;
+
+ byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
+ (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
+ buffer.put(flags);
+
+ buffer.put((byte)mType.length);
+ if (sr) {
+ buffer.put((byte)mPayload.length);
+ } else {
+ buffer.putInt(mPayload.length);
+ }
+ if (il) {
+ buffer.put((byte)mId.length);
+ }
+
+ buffer.put(mType);
+ buffer.put(mId);
+ buffer.put(mPayload);
+ }
+
+ /**
+ * Get byte length of serialized record.
+ */
+ int getByteLength() {
+ int length = 3 + mType.length + mId.length + mPayload.length;
+
+ boolean sr = mPayload.length < 256;
+ boolean il = mId.length > 0;
+
+ if (!sr) length += 3;
+ if (il) length += 1;
+
+ return length;
+ }
+
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mFlags);
dest.writeInt(mTnf);
dest.writeInt(mType.length);
dest.writeByteArray(mType);
@@ -442,8 +838,8 @@ public final class NdefRecord implements Parcelable {
public static final Parcelable.Creator
* @deprecated use {@link #getDefaultAdapter(Context)}
+ * @hide
*/
@Deprecated
public static NfcAdapter getDefaultAdapter() {
+ // introduce in API version 9 (GB 2.3)
+ // deprecated in API version 10 (GB 2.3.3)
+ // removed from public API in version 16 (ICS MR2)
+ // will need to maintain this as a hidden API for a while longer...
Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
"NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index bafee0e81ff9c..8be1996d67a2c 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -76,8 +76,6 @@ LOCAL_SRC_FILES:= \
android_net_TrafficStats.cpp \
android_net_wifi_Wifi.cpp \
android_nio_utils.cpp \
- android_nfc_NdefMessage.cpp \
- android_nfc_NdefRecord.cpp \
android_text_format_Time.cpp \
android_util_AssetManager.cpp \
android_util_Binder.cpp \
@@ -214,7 +212,6 @@ LOCAL_SHARED_LIBRARIES := \
libmedia \
libwpa_client \
libjpeg \
- libnfc_ndef \
libusbhost \
libharfbuzz \
libz \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8db7b248a6be3..af37454040014 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -129,8 +129,6 @@ extern int register_android_database_SQLiteQuery(JNIEnv* env);
extern int register_android_database_SQLiteStatement(JNIEnv* env);
extern int register_android_debug_JNITest(JNIEnv* env);
extern int register_android_nio_utils(JNIEnv* env);
-extern int register_android_nfc_NdefMessage(JNIEnv *env);
-extern int register_android_nfc_NdefRecord(JNIEnv *env);
extern int register_android_text_format_Time(JNIEnv* env);
extern int register_android_os_Debug(JNIEnv* env);
extern int register_android_os_MessageQueue(JNIEnv* env);
@@ -1161,8 +1159,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_net_NetworkUtils),
REG_JNI(register_android_net_TrafficStats),
REG_JNI(register_android_net_wifi_WifiManager),
- REG_JNI(register_android_nfc_NdefMessage),
- REG_JNI(register_android_nfc_NdefRecord),
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_android_hardware_Camera),
diff --git a/core/jni/android_nfc_NdefMessage.cpp b/core/jni/android_nfc_NdefMessage.cpp
deleted file mode 100644
index 41099cb503af8..0000000000000
--- a/core/jni/android_nfc_NdefMessage.cpp
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#include
- *
- *
+ *
+ * This minimal validation is specified by
+ * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).