blob: 2b588185947c01cc877eeef4f9e3b27a148a59c1 [file] [log] [blame]
Nick Pellydc993792010-10-04 11:17:25 -07001/*
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
Nick Pellyc97a5522012-01-05 15:13:01 +110019import android.content.Intent;
Nick Pellye0180d02011-06-07 17:27:44 -070020import android.net.Uri;
Nick Pellydc993792010-10-04 11:17:25 -070021import android.os.Parcel;
22import android.os.Parcelable;
Nick Pellya356bf12011-12-13 15:36:31 -080023import java.nio.BufferUnderflowException;
24import java.nio.ByteBuffer;
Elliott Hughesd396a442013-06-28 16:24:48 -070025import java.nio.charset.StandardCharsets;
Nick Pellya356bf12011-12-13 15:36:31 -080026import java.util.ArrayList;
Nick Pellye0180d02011-06-07 17:27:44 -070027import java.util.Arrays;
Nick Pellya356bf12011-12-13 15:36:31 -080028import java.util.List;
Nick Pellyc97a5522012-01-05 15:13:01 +110029import java.util.Locale;
Nick Pellydc993792010-10-04 11:17:25 -070030
31/**
Nick Pellya356bf12011-12-13 15:36:31 -080032 * Represents an immutable NDEF Record.
33 * <p>
34 * NDEF (NFC Data Exchange Format) is a light-weight binary format,
35 * used to encapsulate typed data. It is specified by the NFC Forum,
36 * for transmission and storage with NFC, however it is transport agnostic.
37 * <p>
38 * NDEF defines messages and records. An NDEF Record contains
39 * typed data, such as MIME-type media, a URI, or a custom
40 * application payload. An NDEF Message is a container for
41 * one or more NDEF Records.
42 * <p>
43 * This class represents logical (complete) NDEF Records, and can not be
44 * used to represent chunked (partial) NDEF Records. However
45 * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
46 * containing chunked records, and will return a message with unchunked
47 * (complete) records.
48 * <p>
49 * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
50 * that provides high level typing for the rest of the record. The
51 * remaining fields are variable length and not always present:
Nick Pellydc993792010-10-04 11:17:25 -070052 * <ul>
Nick Pellya356bf12011-12-13 15:36:31 -080053 * <li><em>type</em>: detailed typing for the payload</li>
54 * <li><em>id</em>: identifier meta-data, not commonly used</li>
55 * <li><em>payload</em>: the actual payload</li>
Nick Pellydc993792010-10-04 11:17:25 -070056 * </ul>
Nick Pellya356bf12011-12-13 15:36:31 -080057 * <p>
58 * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
59 * and {@link NdefRecord#createExternal} are included to create well-formatted
60 * NDEF Records with correctly set tnf, type, id and payload fields, please
61 * use these helpers whenever possible.
62 * <p>
63 * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
64 * if you know what you are doing and what to set the fields individually.
65 * Only basic validation is performed with this constructor, so it is possible
66 * to create records that do not confirm to the strict NFC Forum
67 * specifications.
68 * <p>
69 * The binary representation of an NDEF Record includes additional flags to
70 * indicate location with an NDEF message, provide support for chunking of
71 * NDEF records, and to pack optional fields. This class does not expose
72 * those details. To write an NDEF Record as binary you must first put it
Jeff Smitha45746e2012-07-19 14:19:24 -050073 * into an {@link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
Nick Pellya356bf12011-12-13 15:36:31 -080074 * <p class="note">
75 * {@link NdefMessage} and {@link NdefRecord} implementations are
76 * always available, even on Android devices that do not have NFC hardware.
77 * <p class="note">
78 * {@link NdefRecord}s are intended to be immutable (and thread-safe),
79 * however they may contain mutable fields. So take care not to modify
80 * mutable fields passed into constructors, or modify mutable fields
81 * obtained by getter methods, unless such modification is explicitly
82 * marked as safe.
83 *
84 * @see NfcAdapter#ACTION_NDEF_DISCOVERED
85 * @see NdefMessage
Nick Pellydc993792010-10-04 11:17:25 -070086 */
Nick Pelly11b075e2010-10-28 13:39:37 -070087public final class NdefRecord implements Parcelable {
Nick Pellydc993792010-10-04 11:17:25 -070088 /**
Nick Pellya356bf12011-12-13 15:36:31 -080089 * Indicates the record is empty.<p>
90 * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
Nick Pellydc993792010-10-04 11:17:25 -070091 */
92 public static final short TNF_EMPTY = 0x00;
93
94 /**
Nick Pellya356bf12011-12-13 15:36:31 -080095 * Indicates the type field contains a well-known RTD type name.<p>
96 * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
Nick Pellydc993792010-10-04 11:17:25 -070097 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -080098 * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
99 *
100 * @see #RTD_URI
101 * @see #RTD_TEXT
102 * @see #RTD_SMART_POSTER
103 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700104 */
105 public static final short TNF_WELL_KNOWN = 0x01;
106
107 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800108 * Indicates the type field contains a media-type BNF
109 * construct, defined by RFC 2046.<p>
110 * Use this with MIME type names such as {@literal "image/jpeg"}, or
111 * using the helper {@link #createMime}.
112 *
113 * @see #createMime
Nick Pellydc993792010-10-04 11:17:25 -0700114 */
115 public static final short TNF_MIME_MEDIA = 0x02;
116
117 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800118 * Indicates the type field contains an absolute-URI
119 * BNF construct defined by RFC 3986.<p>
120 * When creating new records prefer {@link #createUri},
121 * since it offers more compact URI encoding
122 * ({@literal #RTD_URI} allows compression of common URI prefixes).
123 *
124 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700125 */
126 public static final short TNF_ABSOLUTE_URI = 0x03;
127
128 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800129 * Indicates the type field contains an external type name.<p>
130 * Used to encode custom payloads. When creating new records
131 * use the helper {@link #createExternal}.<p>
132 * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700133 * <p>
134 * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
135 * Those are well known RTD constants, not external RTD constants.
Nick Pellya356bf12011-12-13 15:36:31 -0800136 *
137 * @see #createExternal
Nick Pellydc993792010-10-04 11:17:25 -0700138 */
139 public static final short TNF_EXTERNAL_TYPE = 0x04;
140
141 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800142 * Indicates the payload type is unknown.<p>
143 * NFC Forum explains this should be treated similarly to the
144 * "application/octet-stream" MIME type. The payload
145 * type is not explicitly encoded within the record.
Nick Pellydc993792010-10-04 11:17:25 -0700146 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800147 * The type field is empty in an {@literal TNF_UNKNOWN} record.
Nick Pellydc993792010-10-04 11:17:25 -0700148 */
149 public static final short TNF_UNKNOWN = 0x05;
150
151 /**
152 * Indicates the payload is an intermediate or final chunk of a chunked
Nick Pellya356bf12011-12-13 15:36:31 -0800153 * NDEF Record.<p>
154 * {@literal TNF_UNCHANGED} can not be used with this class
155 * since all {@link NdefRecord}s are already unchunked, however they
156 * may appear in the binary format.
Nick Pellydc993792010-10-04 11:17:25 -0700157 */
158 public static final short TNF_UNCHANGED = 0x06;
159
160 /**
161 * Reserved TNF type.
162 * <p>
163 * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
164 * value like TNF_UNKNOWN.
165 * @hide
166 */
167 public static final short TNF_RESERVED = 0x07;
168
169 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800170 * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
171 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700172 */
173 public static final byte[] RTD_TEXT = {0x54}; // "T"
174
175 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800176 * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
177 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700178 */
179 public static final byte[] RTD_URI = {0x55}; // "U"
180
181 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800182 * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
183 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700184 */
185 public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
186
187 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800188 * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
189 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700190 */
191 public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
192
193 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800194 * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
195 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700196 */
197 public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
198
199 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800200 * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
201 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700202 */
203 public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
204
205 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800206 * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
207 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700208 */
209 public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
210
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700211 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800212 * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700213 * <p>
214 * The payload of a record with type RTD_ANDROID_APP
215 * should be the package name identifying an application.
216 * Multiple RTD_ANDROID_APP records may be included
217 * in a single {@link NdefMessage}.
218 * <p>
219 * Use {@link #createApplicationRecord(String)} to create
220 * RTD_ANDROID_APP records.
221 * @hide
222 */
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700223 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
224
Nick Pelly590b73b2010-10-12 13:00:50 -0700225 private static final byte FLAG_MB = (byte) 0x80;
226 private static final byte FLAG_ME = (byte) 0x40;
227 private static final byte FLAG_CF = (byte) 0x20;
228 private static final byte FLAG_SR = (byte) 0x10;
229 private static final byte FLAG_IL = (byte) 0x08;
230
Nick Pellye0180d02011-06-07 17:27:44 -0700231 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800232 * NFC Forum "URI Record Type Definition"<p>
Nick Pellye0180d02011-06-07 17:27:44 -0700233 * This is a mapping of "URI Identifier Codes" to URI string prefixes,
234 * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
235 */
236 private static final String[] URI_PREFIX_MAP = new String[] {
237 "", // 0x00
238 "http://www.", // 0x01
239 "https://www.", // 0x02
240 "http://", // 0x03
241 "https://", // 0x04
242 "tel:", // 0x05
243 "mailto:", // 0x06
244 "ftp://anonymous:anonymous@", // 0x07
245 "ftp://ftp.", // 0x08
246 "ftps://", // 0x09
247 "sftp://", // 0x0A
248 "smb://", // 0x0B
249 "nfs://", // 0x0C
250 "ftp://", // 0x0D
251 "dav://", // 0x0E
252 "news:", // 0x0F
253 "telnet://", // 0x10
254 "imap:", // 0x11
255 "rtsp://", // 0x12
256 "urn:", // 0x13
257 "pop:", // 0x14
258 "sip:", // 0x15
259 "sips:", // 0x16
260 "tftp:", // 0x17
261 "btspp://", // 0x18
262 "btl2cap://", // 0x19
263 "btgoep://", // 0x1A
264 "tcpobex://", // 0x1B
265 "irdaobex://", // 0x1C
266 "file://", // 0x1D
267 "urn:epc:id:", // 0x1E
268 "urn:epc:tag:", // 0x1F
269 "urn:epc:pat:", // 0x20
270 "urn:epc:raw:", // 0x21
271 "urn:epc:", // 0x22
272 };
273
Nick Pellya356bf12011-12-13 15:36:31 -0800274 private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
275
276 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
277
Nick Pelly590b73b2010-10-12 13:00:50 -0700278 private final short mTnf;
279 private final byte[] mType;
280 private final byte[] mId;
281 private final byte[] mPayload;
282
Nick Pellydc993792010-10-04 11:17:25 -0700283 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800284 * Create a new Android Application Record (AAR).
Nick Pellydc993792010-10-04 11:17:25 -0700285 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800286 * This record indicates to other Android devices the package
287 * that should be used to handle the entire NDEF message.
288 * You can embed this record anywhere into your message
289 * to ensure that the intended package receives the message.
290 * <p>
291 * When an Android device dispatches an {@link NdefMessage}
292 * containing one or more Android application records,
293 * the applications contained in those records will be the
294 * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
295 * intent, in the order in which they appear in the message.
296 * This dispatch behavior was first added to Android in
297 * Ice Cream Sandwich.
298 * <p>
299 * If none of the applications have a are installed on the device,
300 * a Market link will be opened to the first application.
301 * <p>
302 * Note that Android application records do not overrule
303 * applications that have called
304 * {@link NfcAdapter#enableForegroundDispatch}.
305 *
306 * @param packageName Android package name
307 * @return Android application NDEF record
308 */
309 public static NdefRecord createApplicationRecord(String packageName) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100310 if (packageName == null) throw new NullPointerException("packageName is null");
311 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
312
Nick Pellya356bf12011-12-13 15:36:31 -0800313 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
Elliott Hughesd396a442013-06-28 16:24:48 -0700314 packageName.getBytes(StandardCharsets.UTF_8));
Nick Pellya356bf12011-12-13 15:36:31 -0800315 }
316
317 /**
318 * Create a new NDEF Record containing a URI.<p>
319 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
320 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
321 * and {@link #RTD_URI}. This is the most efficient encoding
322 * of a URI into NDEF.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100323 * The uri parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400324 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100325 * follow Android best practices for intent filtering.
326 * However the unchecked exception
327 * {@link IllegalArgumentException} may be thrown if the uri
328 * parameter has serious problems, for example if it is empty, so always
329 * catch this exception if you are passing user-generated data into this
330 * method.<p>
331 *
Nick Pellya356bf12011-12-13 15:36:31 -0800332 * Reference specification: NFCForum-TS-RTD_URI_1.0
333 *
334 * @param uri URI to encode.
335 * @return an NDEF Record containing the URI
Nick Pellyc97a5522012-01-05 15:13:01 +1100336 * @throws IllegalArugmentException if the uri is empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800337 */
338 public static NdefRecord createUri(Uri uri) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100339 if (uri == null) throw new NullPointerException("uri is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800340
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400341 uri = uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100342 String uriString = uri.toString();
343 if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
Nick Pellya356bf12011-12-13 15:36:31 -0800344
345 byte prefix = 0;
346 for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
347 if (uriString.startsWith(URI_PREFIX_MAP[i])) {
348 prefix = (byte) i;
349 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
350 break;
351 }
352 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700353 byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800354 byte[] recordBytes = new byte[uriBytes.length + 1];
355 recordBytes[0] = prefix;
356 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
357 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
358 }
359
360 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100361 * Create a new NDEF Record containing a URI.<p>
362 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
363 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
364 * and {@link #RTD_URI}. This is the most efficient encoding
365 * of a URI into NDEF.<p>
366 * The uriString parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400367 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100368 * follow Android best practices for intent filtering.
369 * However the unchecked exception
370 * {@link IllegalArgumentException} may be thrown if the uriString
371 * parameter has serious problems, for example if it is empty, so always
372 * catch this exception if you are passing user-generated data into this
373 * method.<p>
374 *
375 * Reference specification: NFCForum-TS-RTD_URI_1.0
376 *
377 * @param uriString string URI to encode.
378 * @return an NDEF Record containing the URI
379 * @throws IllegalArugmentException if the uriString is empty or invalid
380 */
381 public static NdefRecord createUri(String uriString) {
382 return createUri(Uri.parse(uriString));
383 }
384
385 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800386 * Create a new NDEF Record containing MIME data.<p>
387 * Use this method to encode MIME-typed data into an NDEF Record,
388 * such as "text/plain", or "image/jpeg".<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100389 * The mimeType parameter will be normalized with
390 * {@link Intent#normalizeMimeType} to follow Android best
391 * practices for intent filtering, for example to force lower-case.
392 * However the unchecked exception
393 * {@link IllegalArgumentException} may be thrown
394 * if the mimeType parameter has serious problems,
395 * for example if it is empty, so always catch this
396 * exception if you are passing user-generated data into this method.
397 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800398 * For efficiency, This method might not make an internal copy of the
399 * mimeData byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100400 * to modify the mimeData byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800401 * NdefRecord.
402 *
Nick Pellyc97a5522012-01-05 15:13:01 +1100403 * @param mimeType a valid MIME type
Nick Pellya356bf12011-12-13 15:36:31 -0800404 * @param mimeData MIME data as bytes
405 * @return an NDEF Record containing the MIME-typed data
Nick Pellyc97a5522012-01-05 15:13:01 +1100406 * @throws IllegalArugmentException if the mimeType is empty or invalid
407 *
Nick Pellya356bf12011-12-13 15:36:31 -0800408 */
409 public static NdefRecord createMime(String mimeType, byte[] mimeData) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100410 if (mimeType == null) throw new NullPointerException("mimeType is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800411
Nick Pellyc97a5522012-01-05 15:13:01 +1100412 // We only do basic MIME type validation: trying to follow the
413 // RFCs strictly only ends in tears, since there are lots of MIME
414 // types in common use that are not strictly valid as per RFC rules
415 mimeType = Intent.normalizeMimeType(mimeType);
416 if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
417 int slashIndex = mimeType.indexOf('/');
418 if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
419 if (slashIndex == mimeType.length() - 1) {
420 throw new IllegalArgumentException("mimeType must have minor type");
421 }
422 // missing '/' is allowed
423
424 // MIME RFCs suggest ASCII encoding for content-type
Elliott Hughesd396a442013-06-28 16:24:48 -0700425 byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100426 return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
Nick Pellya356bf12011-12-13 15:36:31 -0800427 }
428
429 /**
430 * Create a new NDEF Record containing external (application-specific) data.<p>
431 * Use this method to encode application specific data into an NDEF Record.
432 * The data is typed by a domain name (usually your Android package name) and
433 * a domain-specific type. This data is packaged into a "NFC Forum External
434 * Type" NDEF Record.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100435 * NFC Forum requires that the domain and type used in an external record
436 * are treated as case insensitive, however Android intent filtering is
437 * always case sensitive. So this method will force the domain and type to
438 * lower-case before creating the NDEF Record.<p>
439 * The unchecked exception {@link IllegalArgumentException} will be thrown
440 * if the domain and type have serious problems, for example if either field
441 * is empty, so always catch this
442 * exception if you are passing user-generated data into this method.<p>
443 * There are no such restrictions on the payload data.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800444 * For efficiency, This method might not make an internal copy of the
445 * data byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100446 * to modify the data byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800447 * NdefRecord.
448 *
449 * Reference specification: NFCForum-TS-RTD_1.0
450 * @param domain domain-name of issuing organization
451 * @param type domain-specific type of data
452 * @param data payload as bytes
Nick Pellyc97a5522012-01-05 15:13:01 +1100453 * @throws IllegalArugmentException if either domain or type are empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800454 */
455 public static NdefRecord createExternal(String domain, String type, byte[] data) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100456 if (domain == null) throw new NullPointerException("domain is null");
457 if (type == null) throw new NullPointerException("type is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800458
Elliott Hughescb64d432013-08-02 10:00:44 -0700459 domain = domain.trim().toLowerCase(Locale.ROOT);
460 type = type.trim().toLowerCase(Locale.ROOT);
Nick Pellyc97a5522012-01-05 15:13:01 +1100461
462 if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
463 if (type.length() == 0) throw new IllegalArgumentException("type is empty");
464
Elliott Hughesd396a442013-06-28 16:24:48 -0700465 byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
466 byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800467 byte[] b = new byte[byteDomain.length + 1 + byteType.length];
468 System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
469 b[byteDomain.length] = ':';
470 System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
471
472 return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
473 }
474
475 /**
476 * Construct an NDEF Record from its component fields.<p>
477 * Recommend to use helpers such as {#createUri} or
478 * {{@link #createExternal} where possible, since they perform
479 * stricter validation that the record is correctly formatted
480 * as per NDEF specifications. However if you know what you are
481 * doing then this constructor offers the most flexibility.<p>
482 * An {@link NdefRecord} represents a logical (complete)
483 * record, and cannot represent NDEF Record chunks.<p>
484 * Basic validation of the tnf, type, id and payload is performed
485 * as per the following rules:
486 * <ul>
487 * <li>The tnf paramter must be a 3-bit value.</li>
488 * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
489 * id or payload.</li>
490 * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
491 * cannot have a type.</li>
492 * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
493 * since this class only represents complete (unchunked) records.</li>
494 * </ul>
495 * This minimal validation is specified by
496 * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
497 * If any of the above validation
498 * steps fail then {@link IllegalArgumentException} is thrown.<p>
499 * Deep inspection of the type, id and payload fields is not
500 * performed, so it is possible to create NDEF Records
501 * that conform to section 3.2.6
502 * but fail other more strict NDEF specification requirements. For
503 * example, the payload may be invalid given the tnf and type.
504 * <p>
505 * To omit a type, id or payload field, set the parameter to an
506 * empty byte array or null.
Nick Pellydc993792010-10-04 11:17:25 -0700507 *
508 * @param tnf a 3-bit TNF constant
Nick Pellya356bf12011-12-13 15:36:31 -0800509 * @param type byte array, containing zero to 255 bytes, or null
510 * @param id byte array, containing zero to 255 bytes, or null
Nick Pellydc993792010-10-04 11:17:25 -0700511 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
Nick Pellya356bf12011-12-13 15:36:31 -0800512 * or null
513 * @throws IllegalArugmentException if a valid record cannot be created
Nick Pellydc993792010-10-04 11:17:25 -0700514 */
515 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
Nick Pellya356bf12011-12-13 15:36:31 -0800516 /* convert nulls */
517 if (type == null) type = EMPTY_BYTE_ARRAY;
518 if (id == null) id = EMPTY_BYTE_ARRAY;
519 if (payload == null) payload = EMPTY_BYTE_ARRAY;
Martijn Coenen8bede172011-04-08 16:42:22 +0200520
Nick Pellya356bf12011-12-13 15:36:31 -0800521 String message = validateTnf(tnf, type, id, payload);
522 if (message != null) {
523 throw new IllegalArgumentException(message);
Nick Pelly590b73b2010-10-12 13:00:50 -0700524 }
525
Nick Pelly590b73b2010-10-12 13:00:50 -0700526 mTnf = tnf;
Nick Pellya356bf12011-12-13 15:36:31 -0800527 mType = type;
528 mId = id;
529 mPayload = payload;
Nick Pellydc993792010-10-04 11:17:25 -0700530 }
531
532 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800533 * Construct an NDEF Record from raw bytes.<p>
534 * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
535 * instead. This is because it does not make sense to parse a record:
536 * the NDEF binary format is only defined for a message, and the
537 * record flags MB and ME do not make sense outside of the context of
538 * an entire message.<p>
539 * This implementation will attempt to parse a single record by ignoring
540 * the MB and ME flags, and otherwise following the rules of
541 * {@link NdefMessage#NdefMessage(byte[])}.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700542 *
Nick Pellya356bf12011-12-13 15:36:31 -0800543 * @param data raw bytes to parse
544 * @throws FormatException if the data cannot be parsed into a valid record
545 * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
Nick Pellydc993792010-10-04 11:17:25 -0700546 */
Nick Pellya356bf12011-12-13 15:36:31 -0800547 @Deprecated
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700548 public NdefRecord(byte[] data) throws FormatException {
Nick Pellya356bf12011-12-13 15:36:31 -0800549 ByteBuffer buffer = ByteBuffer.wrap(data);
550 NdefRecord[] rs = parse(buffer, true);
551
552 if (buffer.remaining() > 0) {
553 throw new FormatException("data too long");
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700554 }
Nick Pellya356bf12011-12-13 15:36:31 -0800555
556 mTnf = rs[0].mTnf;
557 mType = rs[0].mType;
558 mId = rs[0].mId;
559 mPayload = rs[0].mPayload;
Nick Pellydc993792010-10-04 11:17:25 -0700560 }
561
562 /**
563 * Returns the 3-bit TNF.
564 * <p>
565 * TNF is the top-level type.
566 */
567 public short getTnf() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700568 return mTnf;
Nick Pellydc993792010-10-04 11:17:25 -0700569 }
570
571 /**
572 * Returns the variable length Type field.
573 * <p>
574 * This should be used in conjunction with the TNF field to determine the
575 * payload format.
Nick Pellya356bf12011-12-13 15:36:31 -0800576 * <p>
577 * Returns an empty byte array if this record
578 * does not have a type field.
Nick Pellydc993792010-10-04 11:17:25 -0700579 */
580 public byte[] getType() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700581 return mType.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700582 }
583
584 /**
585 * Returns the variable length ID.
Nick Pellya356bf12011-12-13 15:36:31 -0800586 * <p>
587 * Returns an empty byte array if this record
588 * does not have an id field.
Nick Pellydc993792010-10-04 11:17:25 -0700589 */
590 public byte[] getId() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700591 return mId.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700592 }
593
594 /**
595 * Returns the variable length payload.
Nick Pellya356bf12011-12-13 15:36:31 -0800596 * <p>
597 * Returns an empty byte array if this record
598 * does not have a payload field.
Nick Pellydc993792010-10-04 11:17:25 -0700599 */
600 public byte[] getPayload() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700601 return mPayload.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700602 }
603
604 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800605 * Return this NDEF Record as a byte array.<p>
606 * This method is deprecated, use {@link NdefMessage#toByteArray}
607 * instead. This is because the NDEF binary format is not defined for
608 * a record outside of the context of a message: the MB and ME flags
609 * cannot be set without knowing the location inside a message.<p>
610 * This implementation will attempt to serialize a single record by
611 * always setting the MB and ME flags (in other words, assume this
612 * is a single-record NDEF Message).<p>
613 *
614 * @deprecated use {@link NdefMessage#toByteArray()} instead
615 */
616 @Deprecated
617 public byte[] toByteArray() {
618 ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
619 writeToByteBuffer(buffer, true, true);
620 return buffer.array();
621 }
622
623 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100624 * Map this record to a MIME type, or return null if it cannot be mapped.<p>
625 * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
626 * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
627 * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
628 * is returned, otherwise null is returned.<p>
629 * This method does not perform validation that the MIME type is
630 * actually valid. It always attempts to
631 * return a string containing the type if this is a MIME record.<p>
632 * The returned MIME type will by normalized to lower-case using
633 * {@link Intent#normalizeMimeType}.<p>
634 * The MIME payload can be obtained using {@link #getPayload}.
635 *
636 * @return MIME type as a string, or null if this is not a MIME record
Nick Pellye0180d02011-06-07 17:27:44 -0700637 */
Nick Pellyc97a5522012-01-05 15:13:01 +1100638 public String toMimeType() {
639 switch (mTnf) {
640 case NdefRecord.TNF_WELL_KNOWN:
641 if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
642 return "text/plain";
643 }
644 break;
645 case NdefRecord.TNF_MIME_MEDIA:
Elliott Hughesd396a442013-06-28 16:24:48 -0700646 String mimeType = new String(mType, StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100647 return Intent.normalizeMimeType(mimeType);
Nick Pellye0180d02011-06-07 17:27:44 -0700648 }
Nick Pellyc97a5522012-01-05 15:13:01 +1100649 return null;
Nick Pellye0180d02011-06-07 17:27:44 -0700650 }
651
652 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100653 * Map this record to a URI, or return null if it cannot be mapped.<p>
654 * Currently this method considers the following to be URI records:
655 * <ul>
656 * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
657 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
658 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
659 * and containing a URI record in the NDEF message nested in the payload.
660 * </li>
661 * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
662 * </ul>
663 * If this is not a URI record by the above rules, then null is returned.<p>
664 * This method does not perform validation that the URI is
665 * actually valid: it always attempts to create and return a URI if
666 * this record appears to be a URI record by the above rules.<p>
667 * The returned URI will be normalized to have a lower case scheme
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400668 * using {@link Uri#normalizeScheme}.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100669 *
670 * @return URI, or null if this is not a URI record
671 */
672 public Uri toUri() {
673 return toUri(false);
674 }
675
676 private Uri toUri(boolean inSmartPoster) {
677 switch (mTnf) {
678 case TNF_WELL_KNOWN:
679 if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
680 try {
681 // check payload for a nested NDEF Message containing a URI
682 NdefMessage nestedMessage = new NdefMessage(mPayload);
683 for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
684 Uri uri = nestedRecord.toUri(true);
685 if (uri != null) {
686 return uri;
687 }
688 }
689 } catch (FormatException e) { }
690 } else if (Arrays.equals(mType, RTD_URI)) {
Martijn Coenen32ac1e12012-09-06 17:09:06 +0200691 Uri wktUri = parseWktUri();
692 return (wktUri != null ? wktUri.normalizeScheme() : null);
Nick Pellyc97a5522012-01-05 15:13:01 +1100693 }
694 break;
695
696 case TNF_ABSOLUTE_URI:
Elliott Hughesd396a442013-06-28 16:24:48 -0700697 Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400698 return uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100699
700 case TNF_EXTERNAL_TYPE:
701 if (inSmartPoster) {
702 break;
703 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700704 return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
Nick Pellyc97a5522012-01-05 15:13:01 +1100705 }
706 return null;
707 }
708
709 /**
710 * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
711 * @return complete URI, or null if invalid
712 */
713 private Uri parseWktUri() {
714 if (mPayload.length < 2) {
715 return null;
716 }
717
718 // payload[0] contains the URI Identifier Code, as per
719 // NFC Forum "URI Record Type Definition" section 3.2.2.
720 int prefixIndex = (mPayload[0] & (byte)0xFF);
721 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
722 return null;
723 }
724 String prefix = URI_PREFIX_MAP[prefixIndex];
725 String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
Elliott Hughesd396a442013-06-28 16:24:48 -0700726 StandardCharsets.UTF_8);
Nick Pellyc97a5522012-01-05 15:13:01 +1100727 return Uri.parse(prefix + suffix);
728 }
729
730 /**
731 * Main record parsing method.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800732 * Expects NdefMessage to begin immediately, allows trailing data.<p>
733 * Currently has strict validation of all fields as per NDEF 1.0
734 * specification section 2.5. We will attempt to keep this as strict as
735 * possible to encourage well-formatted NDEF.<p>
736 * Always returns 1 or more NdefRecord's, or throws FormatException.
737 *
738 * @param buffer ByteBuffer to read from
739 * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
740 * @return one or more records
741 * @throws FormatException on any parsing error
Nick Pellydc993792010-10-04 11:17:25 -0700742 */
Nick Pellya356bf12011-12-13 15:36:31 -0800743 static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
744 List<NdefRecord> records = new ArrayList<NdefRecord>();
745
746 try {
747 byte[] type = null;
748 byte[] id = null;
749 byte[] payload = null;
750 ArrayList<byte[]> chunks = new ArrayList<byte[]>();
751 boolean inChunk = false;
752 short chunkTnf = -1;
753 boolean me = false;
754
755 while (!me) {
756 byte flag = buffer.get();
757
758 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
759 me = (flag & NdefRecord.FLAG_ME) != 0;
760 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
761 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
762 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
763 short tnf = (short)(flag & 0x07);
764
765 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
766 throw new FormatException("expected MB flag");
767 } else if (mb && records.size() != 0 && !ignoreMbMe) {
768 throw new FormatException("unexpected MB flag");
769 } else if (inChunk && il) {
770 throw new FormatException("unexpected IL flag in non-leading chunk");
771 } else if (cf && me) {
772 throw new FormatException("unexpected ME flag in non-trailing chunk");
773 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
774 throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
775 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
776 throw new FormatException("" +
777 "unexpected TNF_UNCHANGED in first chunk or unchunked record");
778 }
779
780 int typeLength = buffer.get() & 0xFF;
781 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
782 int idLength = il ? (buffer.get() & 0xFF) : 0;
783
784 if (inChunk && typeLength != 0) {
785 throw new FormatException("expected zero-length type in non-leading chunk");
786 }
787
788 if (!inChunk) {
789 type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
790 id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
791 buffer.get(type);
792 buffer.get(id);
793 }
794
795 ensureSanePayloadSize(payloadLength);
796 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
797 buffer.get(payload);
798
799 if (cf && !inChunk) {
800 // first chunk
801 chunks.clear();
802 chunkTnf = tnf;
803 }
804 if (cf || inChunk) {
805 // any chunk
806 chunks.add(payload);
807 }
808 if (!cf && inChunk) {
809 // last chunk, flatten the payload
810 payloadLength = 0;
811 for (byte[] p : chunks) {
812 payloadLength += p.length;
813 }
814 ensureSanePayloadSize(payloadLength);
815 payload = new byte[(int)payloadLength];
816 int i = 0;
817 for (byte[] p : chunks) {
818 System.arraycopy(p, 0, payload, i, p.length);
819 i += p.length;
820 }
821 tnf = chunkTnf;
822 }
823 if (cf) {
824 // more chunks to come
825 inChunk = true;
826 continue;
827 } else {
828 inChunk = false;
829 }
830
831 String error = validateTnf(tnf, type, id, payload);
832 if (error != null) {
833 throw new FormatException(error);
834 }
835 records.add(new NdefRecord(tnf, type, id, payload));
836 if (ignoreMbMe) { // for parsing a single NdefRecord
837 break;
838 }
839 }
840 } catch (BufferUnderflowException e) {
841 throw new FormatException("expected more data", e);
842 }
843 return records.toArray(new NdefRecord[records.size()]);
Nick Pellydc993792010-10-04 11:17:25 -0700844 }
845
Nick Pellya356bf12011-12-13 15:36:31 -0800846 private static void ensureSanePayloadSize(long size) throws FormatException {
847 if (size > MAX_PAYLOAD_SIZE) {
848 throw new FormatException(
849 "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
850 }
851 }
852
853 /**
854 * Perform simple validation that the tnf is valid.<p>
855 * Validates the requirements of NFCForum-TS-NDEF_1.0 section
856 * 3.2.6 (Type Name Format). This just validates that the tnf
857 * is valid, and that the relevant type, id and payload
858 * fields are present (or empty) for this tnf. It does not
859 * perform any deep inspection of the type, id and payload fields.<p>
860 * Also does not allow TNF_UNCHANGED since this class is only used
861 * to present logical (unchunked) records.
862 *
863 * @return null if valid, or a string error if invalid.
864 */
865 static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
866 switch (tnf) {
867 case TNF_EMPTY:
868 if (type.length != 0 || id.length != 0 || payload.length != 0) {
869 return "unexpected data in TNF_EMPTY record";
870 }
871 return null;
872 case TNF_WELL_KNOWN:
873 case TNF_MIME_MEDIA:
874 case TNF_ABSOLUTE_URI:
875 case TNF_EXTERNAL_TYPE:
876 return null;
877 case TNF_UNKNOWN:
878 case TNF_RESERVED:
879 if (type.length != 0) {
880 return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
881 }
882 return null;
883 case TNF_UNCHANGED:
884 return "unexpected TNF_UNCHANGED in first chunk or logical record";
885 default:
886 return String.format("unexpected tnf value: 0x%02x", tnf);
887 }
888 }
889
890 /**
891 * Serialize record for network transmission.<p>
892 * Uses specified MB and ME flags.<p>
893 * Does not chunk records.
894 */
895 void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
896 boolean sr = mPayload.length < 256;
897 boolean il = mId.length > 0;
898
899 byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
900 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
901 buffer.put(flags);
902
903 buffer.put((byte)mType.length);
904 if (sr) {
905 buffer.put((byte)mPayload.length);
906 } else {
907 buffer.putInt(mPayload.length);
908 }
909 if (il) {
910 buffer.put((byte)mId.length);
911 }
912
913 buffer.put(mType);
914 buffer.put(mId);
915 buffer.put(mPayload);
916 }
917
918 /**
919 * Get byte length of serialized record.
920 */
921 int getByteLength() {
922 int length = 3 + mType.length + mId.length + mPayload.length;
923
924 boolean sr = mPayload.length < 256;
925 boolean il = mId.length > 0;
926
927 if (!sr) length += 3;
928 if (il) length += 1;
929
930 return length;
931 }
932
933 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700934 public int describeContents() {
935 return 0;
936 }
937
Nick Pellya356bf12011-12-13 15:36:31 -0800938 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700939 public void writeToParcel(Parcel dest, int flags) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700940 dest.writeInt(mTnf);
941 dest.writeInt(mType.length);
942 dest.writeByteArray(mType);
943 dest.writeInt(mId.length);
944 dest.writeByteArray(mId);
945 dest.writeInt(mPayload.length);
946 dest.writeByteArray(mPayload);
Nick Pellydc993792010-10-04 11:17:25 -0700947 }
948
949 public static final Parcelable.Creator<NdefRecord> CREATOR =
950 new Parcelable.Creator<NdefRecord>() {
Nick Pellya356bf12011-12-13 15:36:31 -0800951 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700952 public NdefRecord createFromParcel(Parcel in) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700953 short tnf = (short)in.readInt();
954 int typeLength = in.readInt();
955 byte[] type = new byte[typeLength];
956 in.readByteArray(type);
957 int idLength = in.readInt();
958 byte[] id = new byte[idLength];
959 in.readByteArray(id);
960 int payloadLength = in.readInt();
961 byte[] payload = new byte[payloadLength];
962 in.readByteArray(payload);
963
Nick Pellya356bf12011-12-13 15:36:31 -0800964 return new NdefRecord(tnf, type, id, payload);
Nick Pellydc993792010-10-04 11:17:25 -0700965 }
Nick Pellya356bf12011-12-13 15:36:31 -0800966 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700967 public NdefRecord[] newArray(int size) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700968 return new NdefRecord[size];
Nick Pellydc993792010-10-04 11:17:25 -0700969 }
970 };
Nick Pelly590b73b2010-10-12 13:00:50 -0700971
Nick Pellya356bf12011-12-13 15:36:31 -0800972 @Override
973 public int hashCode() {
974 final int prime = 31;
975 int result = 1;
976 result = prime * result + Arrays.hashCode(mId);
977 result = prime * result + Arrays.hashCode(mPayload);
978 result = prime * result + mTnf;
979 result = prime * result + Arrays.hashCode(mType);
980 return result;
981 }
982
983 /**
984 * Returns true if the specified NDEF Record contains
985 * identical tnf, type, id and payload fields.
986 */
987 @Override
988 public boolean equals(Object obj) {
989 if (this == obj) return true;
990 if (obj == null) return false;
991 if (getClass() != obj.getClass()) return false;
992 NdefRecord other = (NdefRecord) obj;
993 if (!Arrays.equals(mId, other.mId)) return false;
994 if (!Arrays.equals(mPayload, other.mPayload)) return false;
995 if (mTnf != other.mTnf) return false;
996 return Arrays.equals(mType, other.mType);
997 }
998
999 @Override
1000 public String toString() {
1001 StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
1002 if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
1003 if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
1004 if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
1005 return b.toString();
1006 }
1007
1008 private static StringBuilder bytesToString(byte[] bs) {
1009 StringBuilder s = new StringBuilder();
1010 for (byte b : bs) {
1011 s.append(String.format("%02X", b));
1012 }
1013 return s;
1014 }
Martijn Coenen8bede172011-04-08 16:42:22 +02001015}