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