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
17package android.nfc;
18
19import java.nio.ByteBuffer;
20import java.util.Arrays;
21
22import android.os.Parcel;
23import 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 */
61public 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}