Home | History | Annotate | Download | only in nfc
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.nfc;
     18 
     19 import android.content.Intent;
     20 import android.net.Uri;
     21 import android.os.Parcel;
     22 import android.os.Parcelable;
     23 
     24 import java.nio.BufferUnderflowException;
     25 import java.nio.ByteBuffer;
     26 import java.nio.charset.StandardCharsets;
     27 import java.util.ArrayList;
     28 import java.util.Arrays;
     29 import java.util.List;
     30 import java.util.Locale;
     31 
     32 /**
     33  * Represents an immutable NDEF Record.
     34  * <p>
     35  * NDEF (NFC Data Exchange Format) is a light-weight binary format,
     36  * used to encapsulate typed data. It is specified by the NFC Forum,
     37  * for transmission and storage with NFC, however it is transport agnostic.
     38  * <p>
     39  * NDEF defines messages and records. An NDEF Record contains
     40  * typed data, such as MIME-type media, a URI, or a custom
     41  * application payload. An NDEF Message is a container for
     42  * one or more NDEF Records.
     43  * <p>
     44  * This class represents logical (complete) NDEF Records, and can not be
     45  * used to represent chunked (partial) NDEF Records. However
     46  * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
     47  * containing chunked records, and will return a message with unchunked
     48  * (complete) records.
     49  * <p>
     50  * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
     51  * that provides high level typing for the rest of the record. The
     52  * remaining fields are variable length and not always present:
     53  * <ul>
     54  * <li><em>type</em>: detailed typing for the payload</li>
     55  * <li><em>id</em>: identifier meta-data, not commonly used</li>
     56  * <li><em>payload</em>: the actual payload</li>
     57  * </ul>
     58  * <p>
     59  * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
     60  * and {@link NdefRecord#createExternal} are included to create well-formatted
     61  * NDEF Records with correctly set tnf, type, id and payload fields, please
     62  * use these helpers whenever possible.
     63  * <p>
     64  * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
     65  * if you know what you are doing and what to set the fields individually.
     66  * Only basic validation is performed with this constructor, so it is possible
     67  * to create records that do not confirm to the strict NFC Forum
     68  * specifications.
     69  * <p>
     70  * The binary representation of an NDEF Record includes additional flags to
     71  * indicate location with an NDEF message, provide support for chunking of
     72  * NDEF records, and to pack optional fields. This class does not expose
     73  * those details. To write an NDEF Record as binary you must first put it
     74  * into an {@link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
     75  * <p class="note">
     76  * {@link NdefMessage} and {@link NdefRecord} implementations are
     77  * always available, even on Android devices that do not have NFC hardware.
     78  * <p class="note">
     79  * {@link NdefRecord}s are intended to be immutable (and thread-safe),
     80  * however they may contain mutable fields. So take care not to modify
     81  * mutable fields passed into constructors, or modify mutable fields
     82  * obtained by getter methods, unless such modification is explicitly
     83  * marked as safe.
     84  *
     85  * @see NfcAdapter#ACTION_NDEF_DISCOVERED
     86  * @see NdefMessage
     87  */
     88 public final class NdefRecord implements Parcelable {
     89     /**
     90      * Indicates the record is empty.<p>
     91      * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
     92      */
     93     public static final short TNF_EMPTY = 0x00;
     94 
     95     /**
     96      * Indicates the type field contains a well-known RTD type name.<p>
     97      * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
     98      * <p>
     99      * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
    100      *
    101      * @see #RTD_URI
    102      * @see #RTD_TEXT
    103      * @see #RTD_SMART_POSTER
    104      * @see #createUri
    105      */
    106     public static final short TNF_WELL_KNOWN = 0x01;
    107 
    108     /**
    109      * Indicates the type field contains a media-type BNF
    110      * construct, defined by RFC 2046.<p>
    111      * Use this with MIME type names such as {@literal "image/jpeg"}, or
    112      * using the helper {@link #createMime}.
    113      *
    114      * @see #createMime
    115      */
    116     public static final short TNF_MIME_MEDIA = 0x02;
    117 
    118     /**
    119      * Indicates the type field contains an absolute-URI
    120      * BNF construct defined by RFC 3986.<p>
    121      * When creating new records prefer {@link #createUri},
    122      * since it offers more compact URI encoding
    123      * ({@literal #RTD_URI} allows compression of common URI prefixes).
    124      *
    125      * @see #createUri
    126      */
    127     public static final short TNF_ABSOLUTE_URI = 0x03;
    128 
    129     /**
    130      * Indicates the type field contains an external type name.<p>
    131      * Used to encode custom payloads. When creating new records
    132      * use the helper {@link #createExternal}.<p>
    133      * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
    134      * <p>
    135      * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
    136      * Those are well known RTD constants, not external RTD constants.
    137      *
    138      * @see #createExternal
    139      */
    140     public static final short TNF_EXTERNAL_TYPE = 0x04;
    141 
    142     /**
    143      * Indicates the payload type is unknown.<p>
    144      * NFC Forum explains this should be treated similarly to the
    145      * "application/octet-stream" MIME type. The payload
    146      * type is not explicitly encoded within the record.
    147      * <p>
    148      * The type field is empty in an {@literal TNF_UNKNOWN} record.
    149      */
    150     public static final short TNF_UNKNOWN = 0x05;
    151 
    152     /**
    153      * Indicates the payload is an intermediate or final chunk of a chunked
    154      * NDEF Record.<p>
    155      * {@literal TNF_UNCHANGED} can not be used with this class
    156      * since all {@link NdefRecord}s are already unchunked, however they
    157      * may appear in the binary format.
    158      */
    159     public static final short TNF_UNCHANGED = 0x06;
    160 
    161     /**
    162      * Reserved TNF type.
    163      * <p>
    164      * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
    165      * value like TNF_UNKNOWN.
    166      * @hide
    167      */
    168     public static final short TNF_RESERVED = 0x07;
    169 
    170     /**
    171      * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
    172      * @see #TNF_WELL_KNOWN
    173      */
    174     public static final byte[] RTD_TEXT = {0x54};  // "T"
    175 
    176     /**
    177      * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
    178      * @see #TNF_WELL_KNOWN
    179      */
    180     public static final byte[] RTD_URI = {0x55};   // "U"
    181 
    182     /**
    183      * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
    184      * @see #TNF_WELL_KNOWN
    185      */
    186     public static final byte[] RTD_SMART_POSTER = {0x53, 0x70};  // "Sp"
    187 
    188     /**
    189      * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
    190      * @see #TNF_WELL_KNOWN
    191      */
    192     public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63};  // "ac"
    193 
    194     /**
    195      * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
    196      * @see #TNF_WELL_KNOWN
    197      */
    198     public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63};  // "Hc"
    199 
    200     /**
    201      * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
    202      * @see #TNF_WELL_KNOWN
    203      */
    204     public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72};  // "Hr"
    205 
    206     /**
    207      * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
    208      * @see #TNF_WELL_KNOWN
    209      */
    210     public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
    211 
    212     /**
    213      * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
    214      * <p>
    215      * The payload of a record with type RTD_ANDROID_APP
    216      * should be the package name identifying an application.
    217      * Multiple RTD_ANDROID_APP records may be included
    218      * in a single {@link NdefMessage}.
    219      * <p>
    220      * Use {@link #createApplicationRecord(String)} to create
    221      * RTD_ANDROID_APP records.
    222      * @hide
    223      */
    224     public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
    225 
    226     private static final byte FLAG_MB = (byte) 0x80;
    227     private static final byte FLAG_ME = (byte) 0x40;
    228     private static final byte FLAG_CF = (byte) 0x20;
    229     private static final byte FLAG_SR = (byte) 0x10;
    230     private static final byte FLAG_IL = (byte) 0x08;
    231 
    232     /**
    233      * NFC Forum "URI Record Type Definition"<p>
    234      * This is a mapping of "URI Identifier Codes" to URI string prefixes,
    235      * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
    236      */
    237     private static final String[] URI_PREFIX_MAP = new String[] {
    238             "", // 0x00
    239             "http://www.", // 0x01
    240             "https://www.", // 0x02
    241             "http://", // 0x03
    242             "https://", // 0x04
    243             "tel:", // 0x05
    244             "mailto:", // 0x06
    245             "ftp://anonymous:anonymous@", // 0x07
    246             "ftp://ftp.", // 0x08
    247             "ftps://", // 0x09
    248             "sftp://", // 0x0A
    249             "smb://", // 0x0B
    250             "nfs://", // 0x0C
    251             "ftp://", // 0x0D
    252             "dav://", // 0x0E
    253             "news:", // 0x0F
    254             "telnet://", // 0x10
    255             "imap:", // 0x11
    256             "rtsp://", // 0x12
    257             "urn:", // 0x13
    258             "pop:", // 0x14
    259             "sip:", // 0x15
    260             "sips:", // 0x16
    261             "tftp:", // 0x17
    262             "btspp://", // 0x18
    263             "btl2cap://", // 0x19
    264             "btgoep://", // 0x1A
    265             "tcpobex://", // 0x1B
    266             "irdaobex://", // 0x1C
    267             "file://", // 0x1D
    268             "urn:epc:id:", // 0x1E
    269             "urn:epc:tag:", // 0x1F
    270             "urn:epc:pat:", // 0x20
    271             "urn:epc:raw:", // 0x21
    272             "urn:epc:", // 0x22
    273             "urn:nfc:", // 0x23
    274     };
    275 
    276     private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20);  // 10 MB payload limit
    277 
    278     private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
    279 
    280     private final short mTnf;
    281     private final byte[] mType;
    282     private final byte[] mId;
    283     private final byte[] mPayload;
    284 
    285     /**
    286      * Create a new Android Application Record (AAR).
    287      * <p>
    288      * This record indicates to other Android devices the package
    289      * that should be used to handle the entire NDEF message.
    290      * You can embed this record anywhere into your message
    291      * to ensure that the intended package receives the message.
    292      * <p>
    293      * When an Android device dispatches an {@link NdefMessage}
    294      * containing one or more Android application records,
    295      * the applications contained in those records will be the
    296      * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
    297      * intent, in the order in which they appear in the message.
    298      * This dispatch behavior was first added to Android in
    299      * Ice Cream Sandwich.
    300      * <p>
    301      * If none of the applications have a are installed on the device,
    302      * a Market link will be opened to the first application.
    303      * <p>
    304      * Note that Android application records do not overrule
    305      * applications that have called
    306      * {@link NfcAdapter#enableForegroundDispatch}.
    307      *
    308      * @param packageName Android package name
    309      * @return Android application NDEF record
    310      */
    311     public static NdefRecord createApplicationRecord(String packageName) {
    312         if (packageName == null) throw new NullPointerException("packageName is null");
    313         if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
    314 
    315         return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
    316                 packageName.getBytes(StandardCharsets.UTF_8));
    317     }
    318 
    319     /**
    320      * Create a new NDEF Record containing a URI.<p>
    321      * Use this method to encode a URI (or URL) into an NDEF Record.<p>
    322      * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
    323      * and {@link #RTD_URI}. This is the most efficient encoding
    324      * of a URI into NDEF.<p>
    325      * The uri parameter will be normalized with
    326      * {@link Uri#normalizeScheme} to set the scheme to lower case to
    327      * follow Android best practices for intent filtering.
    328      * However the unchecked exception
    329      * {@link IllegalArgumentException} may be thrown if the uri
    330      * parameter has serious problems, for example if it is empty, so always
    331      * catch this exception if you are passing user-generated data into this
    332      * method.<p>
    333      *
    334      * Reference specification: NFCForum-TS-RTD_URI_1.0
    335      *
    336      * @param uri URI to encode.
    337      * @return an NDEF Record containing the URI
    338      * @throws IllegalArugmentException if the uri is empty or invalid
    339      */
    340     public static NdefRecord createUri(Uri uri) {
    341         if (uri == null) throw new NullPointerException("uri is null");
    342 
    343         uri = uri.normalizeScheme();
    344         String uriString = uri.toString();
    345         if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
    346 
    347         byte prefix = 0;
    348         for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
    349             if (uriString.startsWith(URI_PREFIX_MAP[i])) {
    350                 prefix = (byte) i;
    351                 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
    352                 break;
    353             }
    354         }
    355         byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
    356         byte[] recordBytes = new byte[uriBytes.length + 1];
    357         recordBytes[0] = prefix;
    358         System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
    359         return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
    360     }
    361 
    362     /**
    363      * Create a new NDEF Record containing a URI.<p>
    364      * Use this method to encode a URI (or URL) into an NDEF Record.<p>
    365      * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
    366      * and {@link #RTD_URI}. This is the most efficient encoding
    367      * of a URI into NDEF.<p>
    368       * The uriString parameter will be normalized with
    369      * {@link Uri#normalizeScheme} to set the scheme to lower case to
    370      * follow Android best practices for intent filtering.
    371      * However the unchecked exception
    372      * {@link IllegalArgumentException} may be thrown if the uriString
    373      * parameter has serious problems, for example if it is empty, so always
    374      * catch this exception if you are passing user-generated data into this
    375      * method.<p>
    376      *
    377      * Reference specification: NFCForum-TS-RTD_URI_1.0
    378      *
    379      * @param uriString string URI to encode.
    380      * @return an NDEF Record containing the URI
    381      * @throws IllegalArugmentException if the uriString is empty or invalid
    382      */
    383     public static NdefRecord createUri(String uriString) {
    384         return createUri(Uri.parse(uriString));
    385     }
    386 
    387     /**
    388      * Create a new NDEF Record containing MIME data.<p>
    389      * Use this method to encode MIME-typed data into an NDEF Record,
    390      * such as "text/plain", or "image/jpeg".<p>
    391      * The mimeType parameter will be normalized with
    392      * {@link Intent#normalizeMimeType} to follow Android best
    393      * practices for intent filtering, for example to force lower-case.
    394      * However the unchecked exception
    395      * {@link IllegalArgumentException} may be thrown
    396      * if the mimeType parameter has serious problems,
    397      * for example if it is empty, so always catch this
    398      * exception if you are passing user-generated data into this method.
    399      * <p>
    400      * For efficiency, This method might not make an internal copy of the
    401      * mimeData byte array, so take care not
    402      * to modify the mimeData byte array while still using the returned
    403      * NdefRecord.
    404      *
    405      * @param mimeType a valid MIME type
    406      * @param mimeData MIME data as bytes
    407      * @return an NDEF Record containing the MIME-typed data
    408      * @throws IllegalArugmentException if the mimeType is empty or invalid
    409      *
    410      */
    411     public static NdefRecord createMime(String mimeType, byte[] mimeData) {
    412         if (mimeType == null) throw new NullPointerException("mimeType is null");
    413 
    414         // We only do basic MIME type validation: trying to follow the
    415         // RFCs strictly only ends in tears, since there are lots of MIME
    416         // types in common use that are not strictly valid as per RFC rules
    417         mimeType = Intent.normalizeMimeType(mimeType);
    418         if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
    419         int slashIndex = mimeType.indexOf('/');
    420         if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
    421         if (slashIndex == mimeType.length() - 1) {
    422             throw new IllegalArgumentException("mimeType must have minor type");
    423         }
    424         // missing '/' is allowed
    425 
    426         // MIME RFCs suggest ASCII encoding for content-type
    427         byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
    428         return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
    429     }
    430 
    431     /**
    432      * Create a new NDEF Record containing external (application-specific) data.<p>
    433      * Use this method to encode application specific data into an NDEF Record.
    434      * The data is typed by a domain name (usually your Android package name) and
    435      * a domain-specific type. This data is packaged into a "NFC Forum External
    436      * Type" NDEF Record.<p>
    437      * NFC Forum requires that the domain and type used in an external record
    438      * are treated as case insensitive, however Android intent filtering is
    439      * always case sensitive. So this method will force the domain and type to
    440      * lower-case before creating the NDEF Record.<p>
    441      * The unchecked exception {@link IllegalArgumentException} will be thrown
    442      * if the domain and type have serious problems, for example if either field
    443      * is empty, so always catch this
    444      * exception if you are passing user-generated data into this method.<p>
    445      * There are no such restrictions on the payload data.<p>
    446      * For efficiency, This method might not make an internal copy of the
    447      * data byte array, so take care not
    448      * to modify the data byte array while still using the returned
    449      * NdefRecord.
    450      *
    451      * Reference specification: NFCForum-TS-RTD_1.0
    452      * @param domain domain-name of issuing organization
    453      * @param type domain-specific type of data
    454      * @param data payload as bytes
    455      * @throws IllegalArugmentException if either domain or type are empty or invalid
    456      */
    457     public static NdefRecord createExternal(String domain, String type, byte[] data) {
    458         if (domain == null) throw new NullPointerException("domain is null");
    459         if (type == null) throw new NullPointerException("type is null");
    460 
    461         domain = domain.trim().toLowerCase(Locale.ROOT);
    462         type = type.trim().toLowerCase(Locale.ROOT);
    463 
    464         if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
    465         if (type.length() == 0) throw new IllegalArgumentException("type is empty");
    466 
    467         byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
    468         byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
    469         byte[] b = new byte[byteDomain.length + 1 + byteType.length];
    470         System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
    471         b[byteDomain.length] = ':';
    472         System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
    473 
    474         return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
    475     }
    476 
    477     /**
    478      * Create a new NDEF record containing UTF-8 text data.<p>
    479      *
    480      * The caller can either specify the language code for the provided text,
    481      * or otherwise the language code corresponding to the current default
    482      * locale will be used.
    483      *
    484      * Reference specification: NFCForum-TS-RTD_Text_1.0
    485      * @param languageCode The languageCode for the record. If locale is empty or null,
    486      *                     the language code of the current default locale will be used.
    487      * @param text   The text to be encoded in the record. Will be represented in UTF-8 format.
    488      * @throws IllegalArgumentException if text is null
    489      */
    490     public static NdefRecord createTextRecord(String languageCode, String text) {
    491         if (text == null) throw new NullPointerException("text is null");
    492 
    493         byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
    494 
    495         byte[] languageCodeBytes = null;
    496         if (languageCode != null && !languageCode.isEmpty()) {
    497             languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII);
    498         } else {
    499             languageCodeBytes = Locale.getDefault().getLanguage().
    500                     getBytes(StandardCharsets.US_ASCII);
    501         }
    502         // We only have 6 bits to indicate ISO/IANA language code.
    503         if (languageCodeBytes.length >= 64) {
    504             throw new IllegalArgumentException("language code is too long, must be <64 bytes.");
    505         }
    506         ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
    507 
    508         byte status = (byte) (languageCodeBytes.length & 0xFF);
    509         buffer.put(status);
    510         buffer.put(languageCodeBytes);
    511         buffer.put(textBytes);
    512 
    513         return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array());
    514     }
    515 
    516     /**
    517      * Construct an NDEF Record from its component fields.<p>
    518      * Recommend to use helpers such as {#createUri} or
    519      * {{@link #createExternal} where possible, since they perform
    520      * stricter validation that the record is correctly formatted
    521      * as per NDEF specifications. However if you know what you are
    522      * doing then this constructor offers the most flexibility.<p>
    523      * An {@link NdefRecord} represents a logical (complete)
    524      * record, and cannot represent NDEF Record chunks.<p>
    525      * Basic validation of the tnf, type, id and payload is performed
    526      * as per the following rules:
    527      * <ul>
    528      * <li>The tnf paramter must be a 3-bit value.</li>
    529      * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
    530      * id or payload.</li>
    531      * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
    532      * cannot have a type.</li>
    533      * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
    534      * since this class only represents complete (unchunked) records.</li>
    535      * </ul>
    536      * This minimal validation is specified by
    537      * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
    538      * If any of the above validation
    539      * steps fail then {@link IllegalArgumentException} is thrown.<p>
    540      * Deep inspection of the type, id and payload fields is not
    541      * performed, so it is possible to create NDEF Records
    542      * that conform to section 3.2.6
    543      * but fail other more strict NDEF specification requirements. For
    544      * example, the payload may be invalid given the tnf and type.
    545      * <p>
    546      * To omit a type, id or payload field, set the parameter to an
    547      * empty byte array or null.
    548      *
    549      * @param tnf  a 3-bit TNF constant
    550      * @param type byte array, containing zero to 255 bytes, or null
    551      * @param id   byte array, containing zero to 255 bytes, or null
    552      * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
    553      *                or null
    554      * @throws IllegalArugmentException if a valid record cannot be created
    555      */
    556     public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
    557         /* convert nulls */
    558         if (type == null) type = EMPTY_BYTE_ARRAY;
    559         if (id == null) id = EMPTY_BYTE_ARRAY;
    560         if (payload == null) payload = EMPTY_BYTE_ARRAY;
    561 
    562         String message = validateTnf(tnf, type, id, payload);
    563         if (message != null) {
    564             throw new IllegalArgumentException(message);
    565         }
    566 
    567         mTnf = tnf;
    568         mType = type;
    569         mId = id;
    570         mPayload = payload;
    571     }
    572 
    573     /**
    574      * Construct an NDEF Record from raw bytes.<p>
    575      * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
    576      * instead. This is because it does not make sense to parse a record:
    577      * the NDEF binary format is only defined for a message, and the
    578      * record flags MB and ME do not make sense outside of the context of
    579      * an entire message.<p>
    580      * This implementation will attempt to parse a single record by ignoring
    581      * the MB and ME flags, and otherwise following the rules of
    582      * {@link NdefMessage#NdefMessage(byte[])}.<p>
    583      *
    584      * @param data raw bytes to parse
    585      * @throws FormatException if the data cannot be parsed into a valid record
    586      * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
    587      */
    588     @Deprecated
    589     public NdefRecord(byte[] data) throws FormatException {
    590         ByteBuffer buffer = ByteBuffer.wrap(data);
    591         NdefRecord[] rs = parse(buffer, true);
    592 
    593         if (buffer.remaining() > 0) {
    594             throw new FormatException("data too long");
    595         }
    596 
    597         mTnf = rs[0].mTnf;
    598         mType = rs[0].mType;
    599         mId = rs[0].mId;
    600         mPayload = rs[0].mPayload;
    601     }
    602 
    603     /**
    604      * Returns the 3-bit TNF.
    605      * <p>
    606      * TNF is the top-level type.
    607      */
    608     public short getTnf() {
    609         return mTnf;
    610     }
    611 
    612     /**
    613      * Returns the variable length Type field.
    614      * <p>
    615      * This should be used in conjunction with the TNF field to determine the
    616      * payload format.
    617      * <p>
    618      * Returns an empty byte array if this record
    619      * does not have a type field.
    620      */
    621     public byte[] getType() {
    622         return mType.clone();
    623     }
    624 
    625     /**
    626      * Returns the variable length ID.
    627      * <p>
    628      * Returns an empty byte array if this record
    629      * does not have an id field.
    630      */
    631     public byte[] getId() {
    632         return mId.clone();
    633     }
    634 
    635     /**
    636      * Returns the variable length payload.
    637      * <p>
    638      * Returns an empty byte array if this record
    639      * does not have a payload field.
    640      */
    641     public byte[] getPayload() {
    642         return mPayload.clone();
    643     }
    644 
    645     /**
    646      * Return this NDEF Record as a byte array.<p>
    647      * This method is deprecated, use {@link NdefMessage#toByteArray}
    648      * instead. This is because the NDEF binary format is not defined for
    649      * a record outside of the context of a message: the MB and ME flags
    650      * cannot be set without knowing the location inside a message.<p>
    651      * This implementation will attempt to serialize a single record by
    652      * always setting the MB and ME flags (in other words, assume this
    653      * is a single-record NDEF Message).<p>
    654      *
    655      * @deprecated use {@link NdefMessage#toByteArray()} instead
    656      */
    657     @Deprecated
    658     public byte[] toByteArray() {
    659         ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
    660         writeToByteBuffer(buffer, true, true);
    661         return buffer.array();
    662     }
    663 
    664     /**
    665      * Map this record to a MIME type, or return null if it cannot be mapped.<p>
    666      * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
    667      * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
    668      * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
    669      * is returned, otherwise null is returned.<p>
    670      * This method does not perform validation that the MIME type is
    671      * actually valid. It always attempts to
    672      * return a string containing the type if this is a MIME record.<p>
    673      * The returned MIME type will by normalized to lower-case using
    674      * {@link Intent#normalizeMimeType}.<p>
    675      * The MIME payload can be obtained using {@link #getPayload}.
    676      *
    677      * @return MIME type as a string, or null if this is not a MIME record
    678      */
    679     public String toMimeType() {
    680         switch (mTnf) {
    681             case NdefRecord.TNF_WELL_KNOWN:
    682                 if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
    683                     return "text/plain";
    684                 }
    685                 break;
    686             case NdefRecord.TNF_MIME_MEDIA:
    687                 String mimeType = new String(mType, StandardCharsets.US_ASCII);
    688                 return Intent.normalizeMimeType(mimeType);
    689         }
    690         return null;
    691     }
    692 
    693     /**
    694      * Map this record to a URI, or return null if it cannot be mapped.<p>
    695      * Currently this method considers the following to be URI records:
    696      * <ul>
    697      * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
    698      * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
    699      * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
    700      * and containing a URI record in the NDEF message nested in the payload.
    701      * </li>
    702      * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
    703      * </ul>
    704      * If this is not a URI record by the above rules, then null is returned.<p>
    705      * This method does not perform validation that the URI is
    706      * actually valid: it always attempts to create and return a URI if
    707      * this record appears to be a URI record by the above rules.<p>
    708      * The returned URI will be normalized to have a lower case scheme
    709      * using {@link Uri#normalizeScheme}.<p>
    710      *
    711      * @return URI, or null if this is not a URI record
    712      */
    713     public Uri toUri() {
    714         return toUri(false);
    715     }
    716 
    717     private Uri toUri(boolean inSmartPoster) {
    718         switch (mTnf) {
    719             case TNF_WELL_KNOWN:
    720                 if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
    721                     try {
    722                         // check payload for a nested NDEF Message containing a URI
    723                         NdefMessage nestedMessage = new NdefMessage(mPayload);
    724                         for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
    725                             Uri uri = nestedRecord.toUri(true);
    726                             if (uri != null) {
    727                                 return uri;
    728                             }
    729                         }
    730                     } catch (FormatException e) {  }
    731                 } else if (Arrays.equals(mType, RTD_URI)) {
    732                     Uri wktUri = parseWktUri();
    733                     return (wktUri != null ? wktUri.normalizeScheme() : null);
    734                 }
    735                 break;
    736 
    737             case TNF_ABSOLUTE_URI:
    738                 Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
    739                 return uri.normalizeScheme();
    740 
    741             case TNF_EXTERNAL_TYPE:
    742                 if (inSmartPoster) {
    743                     break;
    744                 }
    745                 return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
    746         }
    747         return null;
    748     }
    749 
    750     /**
    751      * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
    752      * @return complete URI, or null if invalid
    753      */
    754     private Uri parseWktUri() {
    755         if (mPayload.length < 2) {
    756             return null;
    757         }
    758 
    759         // payload[0] contains the URI Identifier Code, as per
    760         // NFC Forum "URI Record Type Definition" section 3.2.2.
    761         int prefixIndex = (mPayload[0] & (byte)0xFF);
    762         if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
    763             return null;
    764         }
    765         String prefix = URI_PREFIX_MAP[prefixIndex];
    766         String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
    767                 StandardCharsets.UTF_8);
    768         return Uri.parse(prefix + suffix);
    769     }
    770 
    771     /**
    772      * Main record parsing method.<p>
    773      * Expects NdefMessage to begin immediately, allows trailing data.<p>
    774      * Currently has strict validation of all fields as per NDEF 1.0
    775      * specification section 2.5. We will attempt to keep this as strict as
    776      * possible to encourage well-formatted NDEF.<p>
    777      * Always returns 1 or more NdefRecord's, or throws FormatException.
    778      *
    779      * @param buffer ByteBuffer to read from
    780      * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
    781      * @return one or more records
    782      * @throws FormatException on any parsing error
    783      */
    784     static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
    785         List<NdefRecord> records = new ArrayList<NdefRecord>();
    786 
    787         try {
    788             byte[] type = null;
    789             byte[] id = null;
    790             byte[] payload = null;
    791             ArrayList<byte[]> chunks = new ArrayList<byte[]>();
    792             boolean inChunk = false;
    793             short chunkTnf = -1;
    794             boolean me = false;
    795 
    796             while (!me) {
    797                 byte flag = buffer.get();
    798 
    799                 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
    800                 me = (flag & NdefRecord.FLAG_ME) != 0;
    801                 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
    802                 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
    803                 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
    804                 short tnf = (short)(flag & 0x07);
    805 
    806                 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
    807                     throw new FormatException("expected MB flag");
    808                 } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
    809                     throw new FormatException("unexpected MB flag");
    810                 } else if (inChunk && il) {
    811                     throw new FormatException("unexpected IL flag in non-leading chunk");
    812                 } else if (cf && me) {
    813                     throw new FormatException("unexpected ME flag in non-trailing chunk");
    814                 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
    815                     throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
    816                 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
    817                     throw new FormatException("" +
    818                             "unexpected TNF_UNCHANGED in first chunk or unchunked record");
    819                 }
    820 
    821                 int typeLength = buffer.get() & 0xFF;
    822                 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
    823                 int idLength = il ? (buffer.get() & 0xFF) : 0;
    824 
    825                 if (inChunk && typeLength != 0) {
    826                     throw new FormatException("expected zero-length type in non-leading chunk");
    827                 }
    828 
    829                 if (!inChunk) {
    830                     type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
    831                     id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
    832                     buffer.get(type);
    833                     buffer.get(id);
    834                 }
    835 
    836                 ensureSanePayloadSize(payloadLength);
    837                 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
    838                 buffer.get(payload);
    839 
    840                 if (cf && !inChunk) {
    841                     // first chunk
    842                     if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) {
    843                         throw new FormatException("expected non-zero type length in first chunk");
    844                     }
    845                     chunks.clear();
    846                     chunkTnf = tnf;
    847                 }
    848                 if (cf || inChunk) {
    849                     // any chunk
    850                     chunks.add(payload);
    851                 }
    852                 if (!cf && inChunk) {
    853                     // last chunk, flatten the payload
    854                     payloadLength = 0;
    855                     for (byte[] p : chunks) {
    856                         payloadLength += p.length;
    857                     }
    858                     ensureSanePayloadSize(payloadLength);
    859                     payload = new byte[(int)payloadLength];
    860                     int i = 0;
    861                     for (byte[] p : chunks) {
    862                         System.arraycopy(p, 0, payload, i, p.length);
    863                         i += p.length;
    864                     }
    865                     tnf = chunkTnf;
    866                 }
    867                 if (cf) {
    868                     // more chunks to come
    869                     inChunk = true;
    870                     continue;
    871                 } else {
    872                     inChunk = false;
    873                 }
    874 
    875                 String error = validateTnf(tnf, type, id, payload);
    876                 if (error != null) {
    877                     throw new FormatException(error);
    878                 }
    879                 records.add(new NdefRecord(tnf, type, id, payload));
    880                 if (ignoreMbMe) {  // for parsing a single NdefRecord
    881                     break;
    882                 }
    883             }
    884         } catch (BufferUnderflowException e) {
    885             throw new FormatException("expected more data", e);
    886         }
    887         return records.toArray(new NdefRecord[records.size()]);
    888     }
    889 
    890     private static void ensureSanePayloadSize(long size) throws FormatException {
    891         if (size > MAX_PAYLOAD_SIZE) {
    892             throw new FormatException(
    893                     "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
    894         }
    895     }
    896 
    897     /**
    898      * Perform simple validation that the tnf is valid.<p>
    899      * Validates the requirements of NFCForum-TS-NDEF_1.0 section
    900      * 3.2.6 (Type Name Format). This just validates that the tnf
    901      * is valid, and that the relevant type, id and payload
    902      * fields are present (or empty) for this tnf. It does not
    903      * perform any deep inspection of the type, id and payload fields.<p>
    904      * Also does not allow TNF_UNCHANGED since this class is only used
    905      * to present logical (unchunked) records.
    906      *
    907      * @return null if valid, or a string error if invalid.
    908      */
    909     static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
    910         switch (tnf) {
    911             case TNF_EMPTY:
    912                 if (type.length != 0 || id.length != 0 || payload.length != 0) {
    913                     return "unexpected data in TNF_EMPTY record";
    914                 }
    915                 return null;
    916             case TNF_WELL_KNOWN:
    917             case TNF_MIME_MEDIA:
    918             case TNF_ABSOLUTE_URI:
    919             case TNF_EXTERNAL_TYPE:
    920                 return null;
    921             case TNF_UNKNOWN:
    922             case TNF_RESERVED:
    923                 if (type.length != 0) {
    924                     return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
    925                 }
    926                 return null;
    927             case TNF_UNCHANGED:
    928                 return "unexpected TNF_UNCHANGED in first chunk or logical record";
    929             default:
    930                 return String.format("unexpected tnf value: 0x%02x", tnf);
    931         }
    932     }
    933 
    934     /**
    935      * Serialize record for network transmission.<p>
    936      * Uses specified MB and ME flags.<p>
    937      * Does not chunk records.
    938      */
    939     void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
    940         boolean sr = mPayload.length < 256;
    941         boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
    942 
    943         byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
    944                 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
    945         buffer.put(flags);
    946 
    947         buffer.put((byte)mType.length);
    948         if (sr) {
    949             buffer.put((byte)mPayload.length);
    950         } else {
    951             buffer.putInt(mPayload.length);
    952         }
    953         if (il) {
    954             buffer.put((byte)mId.length);
    955         }
    956 
    957         buffer.put(mType);
    958         buffer.put(mId);
    959         buffer.put(mPayload);
    960     }
    961 
    962     /**
    963      * Get byte length of serialized record.
    964      */
    965     int getByteLength() {
    966         int length = 3 + mType.length + mId.length + mPayload.length;
    967 
    968         boolean sr = mPayload.length < 256;
    969         boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
    970 
    971         if (!sr) length += 3;
    972         if (il) length += 1;
    973 
    974         return length;
    975     }
    976 
    977     @Override
    978     public int describeContents() {
    979         return 0;
    980     }
    981 
    982     @Override
    983     public void writeToParcel(Parcel dest, int flags) {
    984         dest.writeInt(mTnf);
    985         dest.writeInt(mType.length);
    986         dest.writeByteArray(mType);
    987         dest.writeInt(mId.length);
    988         dest.writeByteArray(mId);
    989         dest.writeInt(mPayload.length);
    990         dest.writeByteArray(mPayload);
    991     }
    992 
    993     public static final Parcelable.Creator<NdefRecord> CREATOR =
    994             new Parcelable.Creator<NdefRecord>() {
    995         @Override
    996         public NdefRecord createFromParcel(Parcel in) {
    997             short tnf = (short)in.readInt();
    998             int typeLength = in.readInt();
    999             byte[] type = new byte[typeLength];
   1000             in.readByteArray(type);
   1001             int idLength = in.readInt();
   1002             byte[] id = new byte[idLength];
   1003             in.readByteArray(id);
   1004             int payloadLength = in.readInt();
   1005             byte[] payload = new byte[payloadLength];
   1006             in.readByteArray(payload);
   1007 
   1008             return new NdefRecord(tnf, type, id, payload);
   1009         }
   1010         @Override
   1011         public NdefRecord[] newArray(int size) {
   1012             return new NdefRecord[size];
   1013         }
   1014     };
   1015 
   1016     @Override
   1017     public int hashCode() {
   1018         final int prime = 31;
   1019         int result = 1;
   1020         result = prime * result + Arrays.hashCode(mId);
   1021         result = prime * result + Arrays.hashCode(mPayload);
   1022         result = prime * result + mTnf;
   1023         result = prime * result + Arrays.hashCode(mType);
   1024         return result;
   1025     }
   1026 
   1027     /**
   1028      * Returns true if the specified NDEF Record contains
   1029      * identical tnf, type, id and payload fields.
   1030      */
   1031     @Override
   1032     public boolean equals(Object obj) {
   1033         if (this == obj) return true;
   1034         if (obj == null) return false;
   1035         if (getClass() != obj.getClass()) return false;
   1036         NdefRecord other = (NdefRecord) obj;
   1037         if (!Arrays.equals(mId, other.mId)) return false;
   1038         if (!Arrays.equals(mPayload, other.mPayload)) return false;
   1039         if (mTnf != other.mTnf) return false;
   1040         return Arrays.equals(mType, other.mType);
   1041     }
   1042 
   1043     @Override
   1044     public String toString() {
   1045         StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
   1046         if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
   1047         if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
   1048         if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
   1049         return b.toString();
   1050     }
   1051 
   1052     private static StringBuilder bytesToString(byte[] bs) {
   1053         StringBuilder s = new StringBuilder();
   1054         for (byte b : bs) {
   1055             s.append(String.format("%02X", b));
   1056         }
   1057         return s;
   1058     }
   1059 }
   1060