blob: de481cf5642cce848d5298a1ff8bf0a3d2a34b48 [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
Martijn Coenenf180f302013-11-14 13:40:07 -0800272 "urn:nfc:", // 0x23
Nick Pellye0180d02011-06-07 17:27:44 -0700273 };
274
Nick Pellya356bf12011-12-13 15:36:31 -0800275 private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
276
277 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
278
Nick Pelly590b73b2010-10-12 13:00:50 -0700279 private final short mTnf;
280 private final byte[] mType;
281 private final byte[] mId;
282 private final byte[] mPayload;
283
Nick Pellydc993792010-10-04 11:17:25 -0700284 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800285 * Create a new Android Application Record (AAR).
Nick Pellydc993792010-10-04 11:17:25 -0700286 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800287 * This record indicates to other Android devices the package
288 * that should be used to handle the entire NDEF message.
289 * You can embed this record anywhere into your message
290 * to ensure that the intended package receives the message.
291 * <p>
292 * When an Android device dispatches an {@link NdefMessage}
293 * containing one or more Android application records,
294 * the applications contained in those records will be the
295 * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
296 * intent, in the order in which they appear in the message.
297 * This dispatch behavior was first added to Android in
298 * Ice Cream Sandwich.
299 * <p>
300 * If none of the applications have a are installed on the device,
301 * a Market link will be opened to the first application.
302 * <p>
303 * Note that Android application records do not overrule
304 * applications that have called
305 * {@link NfcAdapter#enableForegroundDispatch}.
306 *
307 * @param packageName Android package name
308 * @return Android application NDEF record
309 */
310 public static NdefRecord createApplicationRecord(String packageName) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100311 if (packageName == null) throw new NullPointerException("packageName is null");
312 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
313
Nick Pellya356bf12011-12-13 15:36:31 -0800314 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
Elliott Hughesd396a442013-06-28 16:24:48 -0700315 packageName.getBytes(StandardCharsets.UTF_8));
Nick Pellya356bf12011-12-13 15:36:31 -0800316 }
317
318 /**
319 * Create a new NDEF Record containing a URI.<p>
320 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
321 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
322 * and {@link #RTD_URI}. This is the most efficient encoding
323 * of a URI into NDEF.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100324 * The uri parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400325 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100326 * follow Android best practices for intent filtering.
327 * However the unchecked exception
328 * {@link IllegalArgumentException} may be thrown if the uri
329 * parameter has serious problems, for example if it is empty, so always
330 * catch this exception if you are passing user-generated data into this
331 * method.<p>
332 *
Nick Pellya356bf12011-12-13 15:36:31 -0800333 * Reference specification: NFCForum-TS-RTD_URI_1.0
334 *
335 * @param uri URI to encode.
336 * @return an NDEF Record containing the URI
Nick Pellyc97a5522012-01-05 15:13:01 +1100337 * @throws IllegalArugmentException if the uri is empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800338 */
339 public static NdefRecord createUri(Uri uri) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100340 if (uri == null) throw new NullPointerException("uri is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800341
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400342 uri = uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100343 String uriString = uri.toString();
344 if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
Nick Pellya356bf12011-12-13 15:36:31 -0800345
346 byte prefix = 0;
347 for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
348 if (uriString.startsWith(URI_PREFIX_MAP[i])) {
349 prefix = (byte) i;
350 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
351 break;
352 }
353 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700354 byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800355 byte[] recordBytes = new byte[uriBytes.length + 1];
356 recordBytes[0] = prefix;
357 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
358 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
359 }
360
361 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100362 * Create a new NDEF Record containing a URI.<p>
363 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
364 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
365 * and {@link #RTD_URI}. This is the most efficient encoding
366 * of a URI into NDEF.<p>
367 * The uriString parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400368 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100369 * follow Android best practices for intent filtering.
370 * However the unchecked exception
371 * {@link IllegalArgumentException} may be thrown if the uriString
372 * parameter has serious problems, for example if it is empty, so always
373 * catch this exception if you are passing user-generated data into this
374 * method.<p>
375 *
376 * Reference specification: NFCForum-TS-RTD_URI_1.0
377 *
378 * @param uriString string URI to encode.
379 * @return an NDEF Record containing the URI
380 * @throws IllegalArugmentException if the uriString is empty or invalid
381 */
382 public static NdefRecord createUri(String uriString) {
383 return createUri(Uri.parse(uriString));
384 }
385
386 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800387 * Create a new NDEF Record containing MIME data.<p>
388 * Use this method to encode MIME-typed data into an NDEF Record,
389 * such as "text/plain", or "image/jpeg".<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100390 * The mimeType parameter will be normalized with
391 * {@link Intent#normalizeMimeType} to follow Android best
392 * practices for intent filtering, for example to force lower-case.
393 * However the unchecked exception
394 * {@link IllegalArgumentException} may be thrown
395 * if the mimeType parameter has serious problems,
396 * for example if it is empty, so always catch this
397 * exception if you are passing user-generated data into this method.
398 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800399 * For efficiency, This method might not make an internal copy of the
400 * mimeData byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100401 * to modify the mimeData byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800402 * NdefRecord.
403 *
Nick Pellyc97a5522012-01-05 15:13:01 +1100404 * @param mimeType a valid MIME type
Nick Pellya356bf12011-12-13 15:36:31 -0800405 * @param mimeData MIME data as bytes
406 * @return an NDEF Record containing the MIME-typed data
Nick Pellyc97a5522012-01-05 15:13:01 +1100407 * @throws IllegalArugmentException if the mimeType is empty or invalid
408 *
Nick Pellya356bf12011-12-13 15:36:31 -0800409 */
410 public static NdefRecord createMime(String mimeType, byte[] mimeData) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100411 if (mimeType == null) throw new NullPointerException("mimeType is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800412
Nick Pellyc97a5522012-01-05 15:13:01 +1100413 // We only do basic MIME type validation: trying to follow the
414 // RFCs strictly only ends in tears, since there are lots of MIME
415 // types in common use that are not strictly valid as per RFC rules
416 mimeType = Intent.normalizeMimeType(mimeType);
417 if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
418 int slashIndex = mimeType.indexOf('/');
419 if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
420 if (slashIndex == mimeType.length() - 1) {
421 throw new IllegalArgumentException("mimeType must have minor type");
422 }
423 // missing '/' is allowed
424
425 // MIME RFCs suggest ASCII encoding for content-type
Elliott Hughesd396a442013-06-28 16:24:48 -0700426 byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100427 return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
Nick Pellya356bf12011-12-13 15:36:31 -0800428 }
429
430 /**
431 * Create a new NDEF Record containing external (application-specific) data.<p>
432 * Use this method to encode application specific data into an NDEF Record.
433 * The data is typed by a domain name (usually your Android package name) and
434 * a domain-specific type. This data is packaged into a "NFC Forum External
435 * Type" NDEF Record.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100436 * NFC Forum requires that the domain and type used in an external record
437 * are treated as case insensitive, however Android intent filtering is
438 * always case sensitive. So this method will force the domain and type to
439 * lower-case before creating the NDEF Record.<p>
440 * The unchecked exception {@link IllegalArgumentException} will be thrown
441 * if the domain and type have serious problems, for example if either field
442 * is empty, so always catch this
443 * exception if you are passing user-generated data into this method.<p>
444 * There are no such restrictions on the payload data.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800445 * For efficiency, This method might not make an internal copy of the
446 * data byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100447 * to modify the data byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800448 * NdefRecord.
449 *
450 * Reference specification: NFCForum-TS-RTD_1.0
451 * @param domain domain-name of issuing organization
452 * @param type domain-specific type of data
453 * @param data payload as bytes
Nick Pellyc97a5522012-01-05 15:13:01 +1100454 * @throws IllegalArugmentException if either domain or type are empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800455 */
456 public static NdefRecord createExternal(String domain, String type, byte[] data) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100457 if (domain == null) throw new NullPointerException("domain is null");
458 if (type == null) throw new NullPointerException("type is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800459
Elliott Hughescb64d432013-08-02 10:00:44 -0700460 domain = domain.trim().toLowerCase(Locale.ROOT);
461 type = type.trim().toLowerCase(Locale.ROOT);
Nick Pellyc97a5522012-01-05 15:13:01 +1100462
463 if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
464 if (type.length() == 0) throw new IllegalArgumentException("type is empty");
465
Elliott Hughesd396a442013-06-28 16:24:48 -0700466 byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
467 byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800468 byte[] b = new byte[byteDomain.length + 1 + byteType.length];
469 System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
470 b[byteDomain.length] = ':';
471 System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
472
473 return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
474 }
475
476 /**
477 * Construct an NDEF Record from its component fields.<p>
478 * Recommend to use helpers such as {#createUri} or
479 * {{@link #createExternal} where possible, since they perform
480 * stricter validation that the record is correctly formatted
481 * as per NDEF specifications. However if you know what you are
482 * doing then this constructor offers the most flexibility.<p>
483 * An {@link NdefRecord} represents a logical (complete)
484 * record, and cannot represent NDEF Record chunks.<p>
485 * Basic validation of the tnf, type, id and payload is performed
486 * as per the following rules:
487 * <ul>
488 * <li>The tnf paramter must be a 3-bit value.</li>
489 * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
490 * id or payload.</li>
491 * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
492 * cannot have a type.</li>
493 * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
494 * since this class only represents complete (unchunked) records.</li>
495 * </ul>
496 * This minimal validation is specified by
497 * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
498 * If any of the above validation
499 * steps fail then {@link IllegalArgumentException} is thrown.<p>
500 * Deep inspection of the type, id and payload fields is not
501 * performed, so it is possible to create NDEF Records
502 * that conform to section 3.2.6
503 * but fail other more strict NDEF specification requirements. For
504 * example, the payload may be invalid given the tnf and type.
505 * <p>
506 * To omit a type, id or payload field, set the parameter to an
507 * empty byte array or null.
Nick Pellydc993792010-10-04 11:17:25 -0700508 *
509 * @param tnf a 3-bit TNF constant
Nick Pellya356bf12011-12-13 15:36:31 -0800510 * @param type byte array, containing zero to 255 bytes, or null
511 * @param id byte array, containing zero to 255 bytes, or null
Nick Pellydc993792010-10-04 11:17:25 -0700512 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
Nick Pellya356bf12011-12-13 15:36:31 -0800513 * or null
514 * @throws IllegalArugmentException if a valid record cannot be created
Nick Pellydc993792010-10-04 11:17:25 -0700515 */
516 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
Nick Pellya356bf12011-12-13 15:36:31 -0800517 /* convert nulls */
518 if (type == null) type = EMPTY_BYTE_ARRAY;
519 if (id == null) id = EMPTY_BYTE_ARRAY;
520 if (payload == null) payload = EMPTY_BYTE_ARRAY;
Martijn Coenen8bede172011-04-08 16:42:22 +0200521
Nick Pellya356bf12011-12-13 15:36:31 -0800522 String message = validateTnf(tnf, type, id, payload);
523 if (message != null) {
524 throw new IllegalArgumentException(message);
Nick Pelly590b73b2010-10-12 13:00:50 -0700525 }
526
Nick Pelly590b73b2010-10-12 13:00:50 -0700527 mTnf = tnf;
Nick Pellya356bf12011-12-13 15:36:31 -0800528 mType = type;
529 mId = id;
530 mPayload = payload;
Nick Pellydc993792010-10-04 11:17:25 -0700531 }
532
533 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800534 * Construct an NDEF Record from raw bytes.<p>
535 * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
536 * instead. This is because it does not make sense to parse a record:
537 * the NDEF binary format is only defined for a message, and the
538 * record flags MB and ME do not make sense outside of the context of
539 * an entire message.<p>
540 * This implementation will attempt to parse a single record by ignoring
541 * the MB and ME flags, and otherwise following the rules of
542 * {@link NdefMessage#NdefMessage(byte[])}.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700543 *
Nick Pellya356bf12011-12-13 15:36:31 -0800544 * @param data raw bytes to parse
545 * @throws FormatException if the data cannot be parsed into a valid record
546 * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
Nick Pellydc993792010-10-04 11:17:25 -0700547 */
Nick Pellya356bf12011-12-13 15:36:31 -0800548 @Deprecated
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700549 public NdefRecord(byte[] data) throws FormatException {
Nick Pellya356bf12011-12-13 15:36:31 -0800550 ByteBuffer buffer = ByteBuffer.wrap(data);
551 NdefRecord[] rs = parse(buffer, true);
552
553 if (buffer.remaining() > 0) {
554 throw new FormatException("data too long");
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700555 }
Nick Pellya356bf12011-12-13 15:36:31 -0800556
557 mTnf = rs[0].mTnf;
558 mType = rs[0].mType;
559 mId = rs[0].mId;
560 mPayload = rs[0].mPayload;
Nick Pellydc993792010-10-04 11:17:25 -0700561 }
562
563 /**
564 * Returns the 3-bit TNF.
565 * <p>
566 * TNF is the top-level type.
567 */
568 public short getTnf() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700569 return mTnf;
Nick Pellydc993792010-10-04 11:17:25 -0700570 }
571
572 /**
573 * Returns the variable length Type field.
574 * <p>
575 * This should be used in conjunction with the TNF field to determine the
576 * payload format.
Nick Pellya356bf12011-12-13 15:36:31 -0800577 * <p>
578 * Returns an empty byte array if this record
579 * does not have a type field.
Nick Pellydc993792010-10-04 11:17:25 -0700580 */
581 public byte[] getType() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700582 return mType.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700583 }
584
585 /**
586 * Returns the variable length ID.
Nick Pellya356bf12011-12-13 15:36:31 -0800587 * <p>
588 * Returns an empty byte array if this record
589 * does not have an id field.
Nick Pellydc993792010-10-04 11:17:25 -0700590 */
591 public byte[] getId() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700592 return mId.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700593 }
594
595 /**
596 * Returns the variable length payload.
Nick Pellya356bf12011-12-13 15:36:31 -0800597 * <p>
598 * Returns an empty byte array if this record
599 * does not have a payload field.
Nick Pellydc993792010-10-04 11:17:25 -0700600 */
601 public byte[] getPayload() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700602 return mPayload.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700603 }
604
605 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800606 * Return this NDEF Record as a byte array.<p>
607 * This method is deprecated, use {@link NdefMessage#toByteArray}
608 * instead. This is because the NDEF binary format is not defined for
609 * a record outside of the context of a message: the MB and ME flags
610 * cannot be set without knowing the location inside a message.<p>
611 * This implementation will attempt to serialize a single record by
612 * always setting the MB and ME flags (in other words, assume this
613 * is a single-record NDEF Message).<p>
614 *
615 * @deprecated use {@link NdefMessage#toByteArray()} instead
616 */
617 @Deprecated
618 public byte[] toByteArray() {
619 ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
620 writeToByteBuffer(buffer, true, true);
621 return buffer.array();
622 }
623
624 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100625 * Map this record to a MIME type, or return null if it cannot be mapped.<p>
626 * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
627 * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
628 * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
629 * is returned, otherwise null is returned.<p>
630 * This method does not perform validation that the MIME type is
631 * actually valid. It always attempts to
632 * return a string containing the type if this is a MIME record.<p>
633 * The returned MIME type will by normalized to lower-case using
634 * {@link Intent#normalizeMimeType}.<p>
635 * The MIME payload can be obtained using {@link #getPayload}.
636 *
637 * @return MIME type as a string, or null if this is not a MIME record
Nick Pellye0180d02011-06-07 17:27:44 -0700638 */
Nick Pellyc97a5522012-01-05 15:13:01 +1100639 public String toMimeType() {
640 switch (mTnf) {
641 case NdefRecord.TNF_WELL_KNOWN:
642 if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
643 return "text/plain";
644 }
645 break;
646 case NdefRecord.TNF_MIME_MEDIA:
Elliott Hughesd396a442013-06-28 16:24:48 -0700647 String mimeType = new String(mType, StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100648 return Intent.normalizeMimeType(mimeType);
Nick Pellye0180d02011-06-07 17:27:44 -0700649 }
Nick Pellyc97a5522012-01-05 15:13:01 +1100650 return null;
Nick Pellye0180d02011-06-07 17:27:44 -0700651 }
652
653 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100654 * Map this record to a URI, or return null if it cannot be mapped.<p>
655 * Currently this method considers the following to be URI records:
656 * <ul>
657 * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
658 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
659 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
660 * and containing a URI record in the NDEF message nested in the payload.
661 * </li>
662 * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
663 * </ul>
664 * If this is not a URI record by the above rules, then null is returned.<p>
665 * This method does not perform validation that the URI is
666 * actually valid: it always attempts to create and return a URI if
667 * this record appears to be a URI record by the above rules.<p>
668 * The returned URI will be normalized to have a lower case scheme
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400669 * using {@link Uri#normalizeScheme}.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100670 *
671 * @return URI, or null if this is not a URI record
672 */
673 public Uri toUri() {
674 return toUri(false);
675 }
676
677 private Uri toUri(boolean inSmartPoster) {
678 switch (mTnf) {
679 case TNF_WELL_KNOWN:
680 if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
681 try {
682 // check payload for a nested NDEF Message containing a URI
683 NdefMessage nestedMessage = new NdefMessage(mPayload);
684 for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
685 Uri uri = nestedRecord.toUri(true);
686 if (uri != null) {
687 return uri;
688 }
689 }
690 } catch (FormatException e) { }
691 } else if (Arrays.equals(mType, RTD_URI)) {
Martijn Coenen32ac1e12012-09-06 17:09:06 +0200692 Uri wktUri = parseWktUri();
693 return (wktUri != null ? wktUri.normalizeScheme() : null);
Nick Pellyc97a5522012-01-05 15:13:01 +1100694 }
695 break;
696
697 case TNF_ABSOLUTE_URI:
Elliott Hughesd396a442013-06-28 16:24:48 -0700698 Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400699 return uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100700
701 case TNF_EXTERNAL_TYPE:
702 if (inSmartPoster) {
703 break;
704 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700705 return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
Nick Pellyc97a5522012-01-05 15:13:01 +1100706 }
707 return null;
708 }
709
710 /**
711 * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
712 * @return complete URI, or null if invalid
713 */
714 private Uri parseWktUri() {
715 if (mPayload.length < 2) {
716 return null;
717 }
718
719 // payload[0] contains the URI Identifier Code, as per
720 // NFC Forum "URI Record Type Definition" section 3.2.2.
721 int prefixIndex = (mPayload[0] & (byte)0xFF);
722 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
723 return null;
724 }
725 String prefix = URI_PREFIX_MAP[prefixIndex];
726 String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
Elliott Hughesd396a442013-06-28 16:24:48 -0700727 StandardCharsets.UTF_8);
Nick Pellyc97a5522012-01-05 15:13:01 +1100728 return Uri.parse(prefix + suffix);
729 }
730
731 /**
732 * Main record parsing method.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800733 * Expects NdefMessage to begin immediately, allows trailing data.<p>
734 * Currently has strict validation of all fields as per NDEF 1.0
735 * specification section 2.5. We will attempt to keep this as strict as
736 * possible to encourage well-formatted NDEF.<p>
737 * Always returns 1 or more NdefRecord's, or throws FormatException.
738 *
739 * @param buffer ByteBuffer to read from
740 * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
741 * @return one or more records
742 * @throws FormatException on any parsing error
Nick Pellydc993792010-10-04 11:17:25 -0700743 */
Nick Pellya356bf12011-12-13 15:36:31 -0800744 static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
745 List<NdefRecord> records = new ArrayList<NdefRecord>();
746
747 try {
748 byte[] type = null;
749 byte[] id = null;
750 byte[] payload = null;
751 ArrayList<byte[]> chunks = new ArrayList<byte[]>();
752 boolean inChunk = false;
753 short chunkTnf = -1;
754 boolean me = false;
755
756 while (!me) {
757 byte flag = buffer.get();
758
759 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
760 me = (flag & NdefRecord.FLAG_ME) != 0;
761 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
762 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
763 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
764 short tnf = (short)(flag & 0x07);
765
766 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
767 throw new FormatException("expected MB flag");
768 } else if (mb && records.size() != 0 && !ignoreMbMe) {
769 throw new FormatException("unexpected MB flag");
770 } else if (inChunk && il) {
771 throw new FormatException("unexpected IL flag in non-leading chunk");
772 } else if (cf && me) {
773 throw new FormatException("unexpected ME flag in non-trailing chunk");
774 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
775 throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
776 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
777 throw new FormatException("" +
778 "unexpected TNF_UNCHANGED in first chunk or unchunked record");
779 }
780
781 int typeLength = buffer.get() & 0xFF;
782 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
783 int idLength = il ? (buffer.get() & 0xFF) : 0;
784
785 if (inChunk && typeLength != 0) {
786 throw new FormatException("expected zero-length type in non-leading chunk");
787 }
788
789 if (!inChunk) {
790 type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
791 id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
792 buffer.get(type);
793 buffer.get(id);
794 }
795
796 ensureSanePayloadSize(payloadLength);
797 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
798 buffer.get(payload);
799
800 if (cf && !inChunk) {
801 // first chunk
802 chunks.clear();
803 chunkTnf = tnf;
804 }
805 if (cf || inChunk) {
806 // any chunk
807 chunks.add(payload);
808 }
809 if (!cf && inChunk) {
810 // last chunk, flatten the payload
811 payloadLength = 0;
812 for (byte[] p : chunks) {
813 payloadLength += p.length;
814 }
815 ensureSanePayloadSize(payloadLength);
816 payload = new byte[(int)payloadLength];
817 int i = 0;
818 for (byte[] p : chunks) {
819 System.arraycopy(p, 0, payload, i, p.length);
820 i += p.length;
821 }
822 tnf = chunkTnf;
823 }
824 if (cf) {
825 // more chunks to come
826 inChunk = true;
827 continue;
828 } else {
829 inChunk = false;
830 }
831
832 String error = validateTnf(tnf, type, id, payload);
833 if (error != null) {
834 throw new FormatException(error);
835 }
836 records.add(new NdefRecord(tnf, type, id, payload));
837 if (ignoreMbMe) { // for parsing a single NdefRecord
838 break;
839 }
840 }
841 } catch (BufferUnderflowException e) {
842 throw new FormatException("expected more data", e);
843 }
844 return records.toArray(new NdefRecord[records.size()]);
Nick Pellydc993792010-10-04 11:17:25 -0700845 }
846
Nick Pellya356bf12011-12-13 15:36:31 -0800847 private static void ensureSanePayloadSize(long size) throws FormatException {
848 if (size > MAX_PAYLOAD_SIZE) {
849 throw new FormatException(
850 "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
851 }
852 }
853
854 /**
855 * Perform simple validation that the tnf is valid.<p>
856 * Validates the requirements of NFCForum-TS-NDEF_1.0 section
857 * 3.2.6 (Type Name Format). This just validates that the tnf
858 * is valid, and that the relevant type, id and payload
859 * fields are present (or empty) for this tnf. It does not
860 * perform any deep inspection of the type, id and payload fields.<p>
861 * Also does not allow TNF_UNCHANGED since this class is only used
862 * to present logical (unchunked) records.
863 *
864 * @return null if valid, or a string error if invalid.
865 */
866 static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
867 switch (tnf) {
868 case TNF_EMPTY:
869 if (type.length != 0 || id.length != 0 || payload.length != 0) {
870 return "unexpected data in TNF_EMPTY record";
871 }
872 return null;
873 case TNF_WELL_KNOWN:
874 case TNF_MIME_MEDIA:
875 case TNF_ABSOLUTE_URI:
876 case TNF_EXTERNAL_TYPE:
877 return null;
878 case TNF_UNKNOWN:
879 case TNF_RESERVED:
880 if (type.length != 0) {
881 return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
882 }
883 return null;
884 case TNF_UNCHANGED:
885 return "unexpected TNF_UNCHANGED in first chunk or logical record";
886 default:
887 return String.format("unexpected tnf value: 0x%02x", tnf);
888 }
889 }
890
891 /**
892 * Serialize record for network transmission.<p>
893 * Uses specified MB and ME flags.<p>
894 * Does not chunk records.
895 */
896 void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
897 boolean sr = mPayload.length < 256;
898 boolean il = mId.length > 0;
899
900 byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
901 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
902 buffer.put(flags);
903
904 buffer.put((byte)mType.length);
905 if (sr) {
906 buffer.put((byte)mPayload.length);
907 } else {
908 buffer.putInt(mPayload.length);
909 }
910 if (il) {
911 buffer.put((byte)mId.length);
912 }
913
914 buffer.put(mType);
915 buffer.put(mId);
916 buffer.put(mPayload);
917 }
918
919 /**
920 * Get byte length of serialized record.
921 */
922 int getByteLength() {
923 int length = 3 + mType.length + mId.length + mPayload.length;
924
925 boolean sr = mPayload.length < 256;
926 boolean il = mId.length > 0;
927
928 if (!sr) length += 3;
929 if (il) length += 1;
930
931 return length;
932 }
933
934 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700935 public int describeContents() {
936 return 0;
937 }
938
Nick Pellya356bf12011-12-13 15:36:31 -0800939 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700940 public void writeToParcel(Parcel dest, int flags) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700941 dest.writeInt(mTnf);
942 dest.writeInt(mType.length);
943 dest.writeByteArray(mType);
944 dest.writeInt(mId.length);
945 dest.writeByteArray(mId);
946 dest.writeInt(mPayload.length);
947 dest.writeByteArray(mPayload);
Nick Pellydc993792010-10-04 11:17:25 -0700948 }
949
950 public static final Parcelable.Creator<NdefRecord> CREATOR =
951 new Parcelable.Creator<NdefRecord>() {
Nick Pellya356bf12011-12-13 15:36:31 -0800952 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700953 public NdefRecord createFromParcel(Parcel in) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700954 short tnf = (short)in.readInt();
955 int typeLength = in.readInt();
956 byte[] type = new byte[typeLength];
957 in.readByteArray(type);
958 int idLength = in.readInt();
959 byte[] id = new byte[idLength];
960 in.readByteArray(id);
961 int payloadLength = in.readInt();
962 byte[] payload = new byte[payloadLength];
963 in.readByteArray(payload);
964
Nick Pellya356bf12011-12-13 15:36:31 -0800965 return new NdefRecord(tnf, type, id, payload);
Nick Pellydc993792010-10-04 11:17:25 -0700966 }
Nick Pellya356bf12011-12-13 15:36:31 -0800967 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700968 public NdefRecord[] newArray(int size) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700969 return new NdefRecord[size];
Nick Pellydc993792010-10-04 11:17:25 -0700970 }
971 };
Nick Pelly590b73b2010-10-12 13:00:50 -0700972
Nick Pellya356bf12011-12-13 15:36:31 -0800973 @Override
974 public int hashCode() {
975 final int prime = 31;
976 int result = 1;
977 result = prime * result + Arrays.hashCode(mId);
978 result = prime * result + Arrays.hashCode(mPayload);
979 result = prime * result + mTnf;
980 result = prime * result + Arrays.hashCode(mType);
981 return result;
982 }
983
984 /**
985 * Returns true if the specified NDEF Record contains
986 * identical tnf, type, id and payload fields.
987 */
988 @Override
989 public boolean equals(Object obj) {
990 if (this == obj) return true;
991 if (obj == null) return false;
992 if (getClass() != obj.getClass()) return false;
993 NdefRecord other = (NdefRecord) obj;
994 if (!Arrays.equals(mId, other.mId)) return false;
995 if (!Arrays.equals(mPayload, other.mPayload)) return false;
996 if (mTnf != other.mTnf) return false;
997 return Arrays.equals(mType, other.mType);
998 }
999
1000 @Override
1001 public String toString() {
1002 StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
1003 if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
1004 if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
1005 if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
1006 return b.toString();
1007 }
1008
1009 private static StringBuilder bytesToString(byte[] bs) {
1010 StringBuilder s = new StringBuilder();
1011 for (byte b : bs) {
1012 s.append(String.format("%02X", b));
1013 }
1014 return s;
1015 }
Martijn Coenen8bede172011-04-08 16:42:22 +02001016}