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      */
NdefMessage(byte[] data)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      */
NdefMessage(NdefRecord record, NdefRecord ... records)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      */
NdefMessage(NdefRecord[] records)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      */
getRecords()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      */
getByteArrayLength()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      */
toByteArray()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
describeContents()206     public int describeContents() {
207         return 0;
208     }
209 
210     @Override
writeToParcel(Parcel dest, int flags)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
hashCode()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
equals(Object obj)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
toString()250     public String toString() {
251         return "NdefMessage " + Arrays.toString(mRecords);
252     }
253 }