NdefRecord.java revision 3da3a4582c0793f59a1fd897a992e5e4fd57b6ca
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 android.net.Uri;
20import android.os.Parcel;
21import android.os.Parcelable;
22
23import java.lang.UnsupportedOperationException;
24import java.nio.charset.Charsets;
25import java.util.Arrays;
26
27/**
28 * Represents a logical (unchunked) NDEF (NFC Data Exchange Format) record.
29 * <p>An NDEF record always contains:
30 * <ul>
31 * <li>3-bit TNF (Type Name Format) field: Indicates how to interpret the type field
32 * <li>Variable length type: Describes the record format
33 * <li>Variable length ID: A unique identifier for the record
34 * <li>Variable length payload: The actual data payload
35 * </ul>
36 * <p>The underlying record
37 * representation may be chunked across several NDEF records when the payload is
38 * large.
39 * <p>This is an immutable data class.
40 */
41public final class NdefRecord implements Parcelable {
42    /**
43     * Indicates no type, id, or payload is associated with this NDEF Record.
44     * <p>
45     * Type, id and payload fields must all be empty to be a valid TNF_EMPTY
46     * record.
47     */
48    public static final short TNF_EMPTY = 0x00;
49
50    /**
51     * Indicates the type field uses the RTD type name format.
52     * <p>
53     * Use this TNF with RTD types such as RTD_TEXT, RTD_URI.
54     */
55    public static final short TNF_WELL_KNOWN = 0x01;
56
57    /**
58     * Indicates the type field contains a value that follows the media-type BNF
59     * construct defined by RFC 2046.
60     */
61    public static final short TNF_MIME_MEDIA = 0x02;
62
63    /**
64     * Indicates the type field contains a value that follows the absolute-URI
65     * BNF construct defined by RFC 3986.
66     */
67    public static final short TNF_ABSOLUTE_URI = 0x03;
68
69    /**
70     * Indicates the type field contains a value that follows the RTD external
71     * name specification.
72     * <p>
73     * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
74     * Those are well known RTD constants, not external RTD constants.
75     */
76    public static final short TNF_EXTERNAL_TYPE = 0x04;
77
78    /**
79     * Indicates the payload type is unknown.
80     * <p>
81     * This is similar to the "application/octet-stream" MIME type. The payload
82     * type is not explicitly encoded within the NDEF Message.
83     * <p>
84     * The type field must be empty to be a valid TNF_UNKNOWN record.
85     */
86    public static final short TNF_UNKNOWN = 0x05;
87
88    /**
89     * Indicates the payload is an intermediate or final chunk of a chunked
90     * NDEF Record.
91     * <p>
92     * The payload type is specified in the first chunk, and subsequent chunks
93     * must use TNF_UNCHANGED with an empty type field. TNF_UNCHANGED must not
94     * be used in any other situation.
95     */
96    public static final short TNF_UNCHANGED = 0x06;
97
98    /**
99     * Reserved TNF type.
100     * <p>
101     * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
102     * value like TNF_UNKNOWN.
103     * @hide
104     */
105    public static final short TNF_RESERVED = 0x07;
106
107    /**
108     * RTD Text type. For use with TNF_WELL_KNOWN.
109     */
110    public static final byte[] RTD_TEXT = {0x54};  // "T"
111
112    /**
113     * RTD URI type. For use with TNF_WELL_KNOWN.
114     */
115    public static final byte[] RTD_URI = {0x55};   // "U"
116
117    /**
118     * RTD Smart Poster type. For use with TNF_WELL_KNOWN.
119     */
120    public static final byte[] RTD_SMART_POSTER = {0x53, 0x70};  // "Sp"
121
122    /**
123     * RTD Alternative Carrier type. For use with TNF_WELL_KNOWN.
124     */
125    public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63};  // "ac"
126
127    /**
128     * RTD Handover Carrier type. For use with TNF_WELL_KNOWN.
129     */
130    public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63};  // "Hc"
131
132    /**
133     * RTD Handover Request type. For use with TNF_WELL_KNOWN.
134     */
135    public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72};  // "Hr"
136
137    /**
138     * RTD Handover Select type. For use with TNF_WELL_KNOWN.
139     */
140    public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
141
142    private static final byte FLAG_MB = (byte) 0x80;
143    private static final byte FLAG_ME = (byte) 0x40;
144    private static final byte FLAG_CF = (byte) 0x20;
145    private static final byte FLAG_SR = (byte) 0x10;
146    private static final byte FLAG_IL = (byte) 0x08;
147
148    /**
149     * NFC Forum "URI Record Type Definition"
150     *
151     * This is a mapping of "URI Identifier Codes" to URI string prefixes,
152     * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
153     */
154    private static final String[] URI_PREFIX_MAP = new String[] {
155            "", // 0x00
156            "http://www.", // 0x01
157            "https://www.", // 0x02
158            "http://", // 0x03
159            "https://", // 0x04
160            "tel:", // 0x05
161            "mailto:", // 0x06
162            "ftp://anonymous:anonymous@", // 0x07
163            "ftp://ftp.", // 0x08
164            "ftps://", // 0x09
165            "sftp://", // 0x0A
166            "smb://", // 0x0B
167            "nfs://", // 0x0C
168            "ftp://", // 0x0D
169            "dav://", // 0x0E
170            "news:", // 0x0F
171            "telnet://", // 0x10
172            "imap:", // 0x11
173            "rtsp://", // 0x12
174            "urn:", // 0x13
175            "pop:", // 0x14
176            "sip:", // 0x15
177            "sips:", // 0x16
178            "tftp:", // 0x17
179            "btspp://", // 0x18
180            "btl2cap://", // 0x19
181            "btgoep://", // 0x1A
182            "tcpobex://", // 0x1B
183            "irdaobex://", // 0x1C
184            "file://", // 0x1D
185            "urn:epc:id:", // 0x1E
186            "urn:epc:tag:", // 0x1F
187            "urn:epc:pat:", // 0x20
188            "urn:epc:raw:", // 0x21
189            "urn:epc:", // 0x22
190    };
191
192    private final byte mFlags;
193    private final short mTnf;
194    private final byte[] mType;
195    private final byte[] mId;
196    private final byte[] mPayload;
197
198    /**
199     * Construct an NDEF Record.
200     * <p>
201     * Applications should not attempt to manually chunk NDEF Records - the
202     * implementation of android.nfc will automatically chunk an NDEF Record
203     * when necessary (and only present a single logical NDEF Record to the
204     * application). So applications should not use TNF_UNCHANGED.
205     *
206     * @param tnf  a 3-bit TNF constant
207     * @param type byte array, containing zero to 255 bytes, must not be null
208     * @param id   byte array, containing zero to 255 bytes, must not be null
209     * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
210     *                must not be null
211     */
212    public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
213        /* New NDEF records created by applications will have FLAG_MB|FLAG_ME
214         * set by default; when multiple records are stored in a
215         * {@link NdefMessage}, these flags will be corrected when the {@link NdefMessage}
216         * is serialized to bytes.
217         */
218        this(tnf, type, id, payload, (byte)(FLAG_MB|FLAG_ME));
219    }
220
221    /**
222     * @hide
223     */
224    /*package*/ NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload, byte flags) {
225        /* check arguments */
226        if ((type == null) || (id == null) || (payload == null)) {
227            throw new IllegalArgumentException("Illegal null argument");
228        }
229
230        if (tnf < 0 || tnf > 0x07) {
231            throw new IllegalArgumentException("TNF out of range " + tnf);
232        }
233
234        /* Determine if it is a short record */
235        if(payload.length < 0xFF) {
236            flags |= FLAG_SR;
237        }
238
239        /* Determine if an id is present */
240        if(id.length != 0) {
241            flags |= FLAG_IL;
242        }
243
244        mFlags = flags;
245        mTnf = tnf;
246        mType = type.clone();
247        mId = id.clone();
248        mPayload = payload.clone();
249    }
250
251    /**
252     * Construct an NDEF Record from raw bytes.
253     * <p>
254     * Validation is performed to make sure the header is valid, and that
255     * the id, type and payload sizes appear to be valid.
256     *
257     * @throws FormatException if the data is not a valid NDEF record
258     */
259    public NdefRecord(byte[] data) throws FormatException {
260        /* Prevent compiler to complain about unassigned final fields */
261        mFlags = 0;
262        mTnf = 0;
263        mType = null;
264        mId = null;
265        mPayload = null;
266        /* Perform actual parsing */
267        if (parseNdefRecord(data) == -1) {
268            throw new FormatException("Error while parsing NDEF record");
269        }
270    }
271
272    /**
273     * Returns the 3-bit TNF.
274     * <p>
275     * TNF is the top-level type.
276     */
277    public short getTnf() {
278        return mTnf;
279    }
280
281    /**
282     * Returns the variable length Type field.
283     * <p>
284     * This should be used in conjunction with the TNF field to determine the
285     * payload format.
286     */
287    public byte[] getType() {
288        return mType.clone();
289    }
290
291    /**
292     * Returns the variable length ID.
293     */
294    public byte[] getId() {
295        return mId.clone();
296    }
297
298    /**
299     * Returns the variable length payload.
300     */
301    public byte[] getPayload() {
302        return mPayload.clone();
303    }
304
305    /**
306     * Helper to return the NdefRecord as a URI.
307     * TODO: Consider making a member method instead of static
308     * TODO: Consider more validation that this is a URI record
309     * TODO: Make a public API
310     * @hide
311     */
312    public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException {
313        byte[] payload = record.getPayload();
314        if (payload.length < 2) {
315            throw new FormatException("Payload is not a valid URI (missing prefix)");
316        }
317
318        /*
319         * payload[0] contains the URI Identifier Code, per the
320         * NFC Forum "URI Record Type Definition" section 3.2.2.
321         *
322         * payload[1]...payload[payload.length - 1] contains the rest of
323         * the URI.
324         */
325        int prefixIndex = (payload[0] & 0xff);
326        if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
327            throw new FormatException("Payload is not a valid URI (invalid prefix)");
328        }
329        String prefix = URI_PREFIX_MAP[prefixIndex];
330        byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8),
331                Arrays.copyOfRange(payload, 1, payload.length));
332        return Uri.parse(new String(fullUri, Charsets.UTF_8));
333    }
334
335    /**
336     * Creates an NDEF record of well known type URI.
337     * TODO: Make a public API
338     * @hide
339     */
340    public static NdefRecord createUri(Uri uri) {
341        String uriString = uri.toString();
342        byte prefix = 0x0;
343        for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
344            if (uriString.startsWith(URI_PREFIX_MAP[i])) {
345                prefix = (byte) i;
346                uriString = uriString.substring(URI_PREFIX_MAP[i].length());
347                break;
348            }
349        }
350        byte[] uriBytes = uriString.getBytes(Charsets.UTF_8);
351        byte[] recordBytes = new byte[uriBytes.length + 1];
352        recordBytes[0] = prefix;
353        System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
354        return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, new byte[0], recordBytes);
355    }
356
357    private static byte[] concat(byte[]... arrays) {
358        int length = 0;
359        for (byte[] array : arrays) {
360            length += array.length;
361        }
362        byte[] result = new byte[length];
363        int pos = 0;
364        for (byte[] array : arrays) {
365            System.arraycopy(array, 0, result, pos, array.length);
366            pos += array.length;
367        }
368        return result;
369    }
370
371    /**
372     * Returns this entire NDEF Record as a byte array.
373     */
374    public byte[] toByteArray() {
375        return generate(mFlags, mTnf, mType, mId, mPayload);
376    }
377
378    public int describeContents() {
379        return 0;
380    }
381
382    public void writeToParcel(Parcel dest, int flags) {
383        dest.writeInt(mFlags);
384        dest.writeInt(mTnf);
385        dest.writeInt(mType.length);
386        dest.writeByteArray(mType);
387        dest.writeInt(mId.length);
388        dest.writeByteArray(mId);
389        dest.writeInt(mPayload.length);
390        dest.writeByteArray(mPayload);
391    }
392
393    public static final Parcelable.Creator<NdefRecord> CREATOR =
394            new Parcelable.Creator<NdefRecord>() {
395        public NdefRecord createFromParcel(Parcel in) {
396            byte flags = (byte)in.readInt();
397            short tnf = (short)in.readInt();
398            int typeLength = in.readInt();
399            byte[] type = new byte[typeLength];
400            in.readByteArray(type);
401            int idLength = in.readInt();
402            byte[] id = new byte[idLength];
403            in.readByteArray(id);
404            int payloadLength = in.readInt();
405            byte[] payload = new byte[payloadLength];
406            in.readByteArray(payload);
407
408            return new NdefRecord(tnf, type, id, payload, flags);
409        }
410        public NdefRecord[] newArray(int size) {
411            return new NdefRecord[size];
412        }
413    };
414
415    private native int parseNdefRecord(byte[] data);
416    private native byte[] generate(short flags, short tnf, byte[] type, byte[] id, byte[] data);
417}
418