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