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 java.nio.ByteBuffer;
     20 import java.util.Arrays;
     21 
     22 import android.os.Parcel;
     23 import android.os.Parcelable;
     24 
     25 
     26 /**
     27  * Represents an immutable NDEF Message.
     28  * <p>
     29  * NDEF (NFC Data Exchange Format) is a light-weight binary format,
     30  * used to encapsulate typed data. It is specified by the NFC Forum,
     31  * for transmission and storage with NFC, however it is transport agnostic.
     32  * <p>
     33  * NDEF defines messages and records. An NDEF Record contains
     34  * typed data, such as MIME-type media, a URI, or a custom
     35  * application payload. An NDEF Message is a container for
     36  * one or more NDEF Records.
     37  * <p>
     38  * When an Android device receives an NDEF Message
     39  * (for example by reading an NFC tag) it processes it through
     40  * a dispatch mechanism to determine an activity to launch.
     41  * The type of the <em>first</em> record in the message has
     42  * special importance for message dispatch, so design this record
     43  * carefully.
     44  * <p>
     45  * Use {@link #NdefMessage(byte[])} to construct an NDEF Message from
     46  * binary data, or {@link #NdefMessage(NdefRecord[])} to
     47  * construct from one or more {@link NdefRecord}s.
     48  * <p class="note">
     49  * {@link NdefMessage} and {@link NdefRecord} implementations are
     50  * always available, even on Android devices that do not have NFC hardware.
     51  * <p class="note">
     52  * {@link NdefRecord}s are intended to be immutable (and thread-safe),
     53  * however they may contain mutable fields. So take care not to modify
     54  * mutable fields passed into constructors, or modify mutable fields
     55  * obtained by getter methods, unless such modification is explicitly
     56  * marked as safe.
     57  *
     58  * @see NfcAdapter#ACTION_NDEF_DISCOVERED
     59  * @see NdefRecord
     60  */
     61 public final class NdefMessage implements Parcelable {
     62     private final NdefRecord[] mRecords;
     63 
     64     /**
     65      * Construct an NDEF Message by parsing raw bytes.<p>
     66      * Strict validation of the NDEF binary structure is performed:
     67      * there must be at least one record, every record flag must
     68      * be correct, and the total length of the message must match
     69      * the length of the input data.<p>
     70      * This parser can handle chunked records, and converts them
     71      * into logical {@link NdefRecord}s within the message.<p>
     72      * Once the input data has been parsed to one or more logical
     73      * records, basic validation of the tnf, type, id, and payload fields
     74      * of each record is performed, as per the documentation on
     75      * on {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])}<p>
     76      * If either strict validation of the binary format fails, or
     77      * basic validation during record construction fails, a
     78      * {@link FormatException} is thrown<p>
     79      * Deep inspection of the type, id and payload fields of
     80      * each record is not performed, so it is possible to parse input
     81      * that has a valid binary format and confirms to the basic
     82      * validation requirements of
     83      * {@link NdefRecord#NdefRecord(short, byte[], byte[], byte[])},
     84      * but fails more strict requirements as specified by the
     85      * NFC Forum.
     86      *
     87      * <p class="note">
     88      * It is safe to re-use the data byte array after construction:
     89      * this constructor will make an internal copy of all necessary fields.
     90      *
     91      * @param data raw bytes to parse
     92      * @throws FormatException if the data cannot be parsed
     93      */
     94     public NdefMessage(byte[] data) throws FormatException {
     95         if (data == null) throw new NullPointerException("data is null");
     96         ByteBuffer buffer = ByteBuffer.wrap(data);
     97 
     98         mRecords = NdefRecord.parse(buffer, false);
     99 
    100         if (buffer.remaining() > 0) {
    101             throw new FormatException("trailing data");
    102         }
    103     }
    104 
    105     /**
    106      * Construct an NDEF Message from one or more NDEF Records.
    107      *
    108      * @param record first record (mandatory)
    109      * @param records additional records (optional)
    110      */
    111     public NdefMessage(NdefRecord record, NdefRecord ... records) {
    112         // validate
    113         if (record == null) throw new NullPointerException("record cannot be null");
    114 
    115         for (NdefRecord r : records) {
    116             if (r == null) {
    117                 throw new NullPointerException("record cannot be null");
    118             }
    119         }
    120 
    121         mRecords = new NdefRecord[1 + records.length];
    122         mRecords[0] = record;
    123         System.arraycopy(records, 0, mRecords, 1, records.length);
    124     }
    125 
    126     /**
    127      * Construct an NDEF Message from one or more NDEF Records.
    128      *
    129      * @param records one or more records
    130      */
    131     public NdefMessage(NdefRecord[] records) {
    132         // validate
    133         if (records.length < 1) {
    134             throw new IllegalArgumentException("must have at least one record");
    135         }
    136         for (NdefRecord r : records) {
    137             if (r == null) {
    138                 throw new NullPointerException("records cannot contain null");
    139             }
    140         }
    141 
    142         mRecords = records;
    143     }
    144 
    145     /**
    146      * Get the NDEF Records inside this NDEF Message.<p>
    147      * An {@link NdefMessage} always has one or more NDEF Records: so the
    148      * following code to retrieve the first record is always safe
    149      * (no need to check for null or array length >= 1):
    150      * <pre>
    151      * NdefRecord firstRecord = ndefMessage.getRecords()[0];
    152      * </pre>
    153      *
    154      * @return array of one or more NDEF records.
    155      */
    156     public NdefRecord[] getRecords() {
    157         return mRecords;
    158     }
    159 
    160     /**
    161      * Return the length of this NDEF Message if it is written to a byte array
    162      * with {@link #toByteArray}.<p>
    163      * An NDEF Message can be formatted to bytes in different ways
    164      * depending on chunking, SR, and ID flags, so the length returned
    165      * by this method may not be equal to the length of the original
    166      * byte array used to construct this NDEF Message. However it will
    167      * always be equal to the length of the byte array produced by
    168      * {@link #toByteArray}.
    169      *
    170      * @return length of this NDEF Message when written to bytes with {@link #toByteArray}
    171      * @see #toByteArray
    172      */
    173     public int getByteArrayLength() {
    174         int length = 0;
    175         for (NdefRecord r : mRecords) {
    176             length += r.getByteLength();
    177         }
    178         return length;
    179     }
    180 
    181     /**
    182      * Return this NDEF Message as raw bytes.<p>
    183      * The NDEF Message is formatted as per the NDEF 1.0 specification,
    184      * and the byte array is suitable for network transmission or storage
    185      * in an NFC Forum NDEF compatible tag.<p>
    186      * This method will not chunk any records, and will always use the
    187      * short record (SR) format and omit the identifier field when possible.
    188      *
    189      * @return NDEF Message in binary format
    190      * @see getByteArrayLength
    191      */
    192     public byte[] toByteArray() {
    193         int length = getByteArrayLength();
    194         ByteBuffer buffer = ByteBuffer.allocate(length);
    195 
    196         for (int i=0; i<mRecords.length; i++) {
    197             boolean mb = (i == 0);  // first record
    198             boolean me = (i == mRecords.length - 1);  // last record
    199             mRecords[i].writeToByteBuffer(buffer, mb, me);
    200         }
    201 
    202         return buffer.array();
    203     }
    204 
    205     @Override
    206     public int describeContents() {
    207         return 0;
    208     }
    209 
    210     @Override
    211     public void writeToParcel(Parcel dest, int flags) {
    212         dest.writeInt(mRecords.length);
    213         dest.writeTypedArray(mRecords, flags);
    214     }
    215 
    216     public static final Parcelable.Creator<NdefMessage> CREATOR =
    217             new Parcelable.Creator<NdefMessage>() {
    218         @Override
    219         public NdefMessage createFromParcel(Parcel in) {
    220             int recordsLength = in.readInt();
    221             NdefRecord[] records = new NdefRecord[recordsLength];
    222             in.readTypedArray(records, NdefRecord.CREATOR);
    223             return new NdefMessage(records);
    224         }
    225         @Override
    226         public NdefMessage[] newArray(int size) {
    227             return new NdefMessage[size];
    228         }
    229     };
    230 
    231     @Override
    232     public int hashCode() {
    233         return Arrays.hashCode(mRecords);
    234     }
    235 
    236     /**
    237      * Returns true if the specified NDEF Message contains
    238      * identical NDEF Records.
    239      */
    240     @Override
    241     public boolean equals(Object obj) {
    242         if (this == obj) return true;
    243         if (obj == null) return false;
    244         if (getClass() != obj.getClass()) return false;
    245         NdefMessage other = (NdefMessage) obj;
    246         return Arrays.equals(mRecords, other.mRecords);
    247     }
    248 
    249     @Override
    250     public String toString() {
    251         return "NdefMessage " + Arrays.toString(mRecords);
    252     }
    253 }