blob: 83d17ba7fdd2965d267edea078e6fedce1b0ebc8 [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;
Martijn Coenen7fe9fa12014-01-29 17:28:04 -080023
Nick Pellya356bf12011-12-13 15:36:31 -080024import java.nio.BufferUnderflowException;
25import java.nio.ByteBuffer;
Elliott Hughesd396a442013-06-28 16:24:48 -070026import java.nio.charset.StandardCharsets;
Nick Pellya356bf12011-12-13 15:36:31 -080027import java.util.ArrayList;
Nick Pellye0180d02011-06-07 17:27:44 -070028import java.util.Arrays;
Nick Pellya356bf12011-12-13 15:36:31 -080029import java.util.List;
Nick Pellyc97a5522012-01-05 15:13:01 +110030import java.util.Locale;
Nick Pellydc993792010-10-04 11:17:25 -070031
32/**
Nick Pellya356bf12011-12-13 15:36:31 -080033 * Represents an immutable NDEF Record.
34 * <p>
35 * NDEF (NFC Data Exchange Format) is a light-weight binary format,
36 * used to encapsulate typed data. It is specified by the NFC Forum,
37 * for transmission and storage with NFC, however it is transport agnostic.
38 * <p>
39 * NDEF defines messages and records. An NDEF Record contains
40 * typed data, such as MIME-type media, a URI, or a custom
41 * application payload. An NDEF Message is a container for
42 * one or more NDEF Records.
43 * <p>
44 * This class represents logical (complete) NDEF Records, and can not be
45 * used to represent chunked (partial) NDEF Records. However
46 * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
47 * containing chunked records, and will return a message with unchunked
48 * (complete) records.
49 * <p>
50 * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
51 * that provides high level typing for the rest of the record. The
52 * remaining fields are variable length and not always present:
Nick Pellydc993792010-10-04 11:17:25 -070053 * <ul>
Nick Pellya356bf12011-12-13 15:36:31 -080054 * <li><em>type</em>: detailed typing for the payload</li>
55 * <li><em>id</em>: identifier meta-data, not commonly used</li>
56 * <li><em>payload</em>: the actual payload</li>
Nick Pellydc993792010-10-04 11:17:25 -070057 * </ul>
Nick Pellya356bf12011-12-13 15:36:31 -080058 * <p>
59 * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
60 * and {@link NdefRecord#createExternal} are included to create well-formatted
61 * NDEF Records with correctly set tnf, type, id and payload fields, please
62 * use these helpers whenever possible.
63 * <p>
64 * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
65 * if you know what you are doing and what to set the fields individually.
66 * Only basic validation is performed with this constructor, so it is possible
67 * to create records that do not confirm to the strict NFC Forum
68 * specifications.
69 * <p>
70 * The binary representation of an NDEF Record includes additional flags to
71 * indicate location with an NDEF message, provide support for chunking of
72 * NDEF records, and to pack optional fields. This class does not expose
73 * those details. To write an NDEF Record as binary you must first put it
Jeff Smitha45746e2012-07-19 14:19:24 -050074 * into an {@link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
Nick Pellya356bf12011-12-13 15:36:31 -080075 * <p class="note">
76 * {@link NdefMessage} and {@link NdefRecord} implementations are
77 * always available, even on Android devices that do not have NFC hardware.
78 * <p class="note">
79 * {@link NdefRecord}s are intended to be immutable (and thread-safe),
80 * however they may contain mutable fields. So take care not to modify
81 * mutable fields passed into constructors, or modify mutable fields
82 * obtained by getter methods, unless such modification is explicitly
83 * marked as safe.
84 *
85 * @see NfcAdapter#ACTION_NDEF_DISCOVERED
86 * @see NdefMessage
Nick Pellydc993792010-10-04 11:17:25 -070087 */
Nick Pelly11b075e2010-10-28 13:39:37 -070088public final class NdefRecord implements Parcelable {
Nick Pellydc993792010-10-04 11:17:25 -070089 /**
Nick Pellya356bf12011-12-13 15:36:31 -080090 * Indicates the record is empty.<p>
91 * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
Nick Pellydc993792010-10-04 11:17:25 -070092 */
93 public static final short TNF_EMPTY = 0x00;
94
95 /**
Nick Pellya356bf12011-12-13 15:36:31 -080096 * Indicates the type field contains a well-known RTD type name.<p>
97 * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
Nick Pellydc993792010-10-04 11:17:25 -070098 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -080099 * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
100 *
101 * @see #RTD_URI
102 * @see #RTD_TEXT
103 * @see #RTD_SMART_POSTER
104 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700105 */
106 public static final short TNF_WELL_KNOWN = 0x01;
107
108 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800109 * Indicates the type field contains a media-type BNF
110 * construct, defined by RFC 2046.<p>
111 * Use this with MIME type names such as {@literal "image/jpeg"}, or
112 * using the helper {@link #createMime}.
113 *
114 * @see #createMime
Nick Pellydc993792010-10-04 11:17:25 -0700115 */
116 public static final short TNF_MIME_MEDIA = 0x02;
117
118 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800119 * Indicates the type field contains an absolute-URI
120 * BNF construct defined by RFC 3986.<p>
121 * When creating new records prefer {@link #createUri},
122 * since it offers more compact URI encoding
123 * ({@literal #RTD_URI} allows compression of common URI prefixes).
124 *
125 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700126 */
127 public static final short TNF_ABSOLUTE_URI = 0x03;
128
129 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800130 * Indicates the type field contains an external type name.<p>
131 * Used to encode custom payloads. When creating new records
132 * use the helper {@link #createExternal}.<p>
133 * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700134 * <p>
135 * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
136 * Those are well known RTD constants, not external RTD constants.
Nick Pellya356bf12011-12-13 15:36:31 -0800137 *
138 * @see #createExternal
Nick Pellydc993792010-10-04 11:17:25 -0700139 */
140 public static final short TNF_EXTERNAL_TYPE = 0x04;
141
142 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800143 * Indicates the payload type is unknown.<p>
144 * NFC Forum explains this should be treated similarly to the
145 * "application/octet-stream" MIME type. The payload
146 * type is not explicitly encoded within the record.
Nick Pellydc993792010-10-04 11:17:25 -0700147 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800148 * The type field is empty in an {@literal TNF_UNKNOWN} record.
Nick Pellydc993792010-10-04 11:17:25 -0700149 */
150 public static final short TNF_UNKNOWN = 0x05;
151
152 /**
153 * Indicates the payload is an intermediate or final chunk of a chunked
Nick Pellya356bf12011-12-13 15:36:31 -0800154 * NDEF Record.<p>
155 * {@literal TNF_UNCHANGED} can not be used with this class
156 * since all {@link NdefRecord}s are already unchunked, however they
157 * may appear in the binary format.
Nick Pellydc993792010-10-04 11:17:25 -0700158 */
159 public static final short TNF_UNCHANGED = 0x06;
160
161 /**
162 * Reserved TNF type.
163 * <p>
164 * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
165 * value like TNF_UNKNOWN.
166 * @hide
167 */
168 public static final short TNF_RESERVED = 0x07;
169
170 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800171 * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
172 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700173 */
174 public static final byte[] RTD_TEXT = {0x54}; // "T"
175
176 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800177 * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
178 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700179 */
180 public static final byte[] RTD_URI = {0x55}; // "U"
181
182 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800183 * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
184 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700185 */
186 public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
187
188 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800189 * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
190 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700191 */
192 public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
193
194 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800195 * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
196 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700197 */
198 public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
199
200 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800201 * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
202 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700203 */
204 public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
205
206 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800207 * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
208 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700209 */
210 public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
211
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700212 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800213 * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700214 * <p>
215 * The payload of a record with type RTD_ANDROID_APP
216 * should be the package name identifying an application.
217 * Multiple RTD_ANDROID_APP records may be included
218 * in a single {@link NdefMessage}.
219 * <p>
220 * Use {@link #createApplicationRecord(String)} to create
221 * RTD_ANDROID_APP records.
222 * @hide
223 */
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700224 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
225
Nick Pelly590b73b2010-10-12 13:00:50 -0700226 private static final byte FLAG_MB = (byte) 0x80;
227 private static final byte FLAG_ME = (byte) 0x40;
228 private static final byte FLAG_CF = (byte) 0x20;
229 private static final byte FLAG_SR = (byte) 0x10;
230 private static final byte FLAG_IL = (byte) 0x08;
231
Nick Pellye0180d02011-06-07 17:27:44 -0700232 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800233 * NFC Forum "URI Record Type Definition"<p>
Nick Pellye0180d02011-06-07 17:27:44 -0700234 * This is a mapping of "URI Identifier Codes" to URI string prefixes,
235 * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
236 */
237 private static final String[] URI_PREFIX_MAP = new String[] {
238 "", // 0x00
239 "http://www.", // 0x01
240 "https://www.", // 0x02
241 "http://", // 0x03
242 "https://", // 0x04
243 "tel:", // 0x05
244 "mailto:", // 0x06
245 "ftp://anonymous:anonymous@", // 0x07
246 "ftp://ftp.", // 0x08
247 "ftps://", // 0x09
248 "sftp://", // 0x0A
249 "smb://", // 0x0B
250 "nfs://", // 0x0C
251 "ftp://", // 0x0D
252 "dav://", // 0x0E
253 "news:", // 0x0F
254 "telnet://", // 0x10
255 "imap:", // 0x11
256 "rtsp://", // 0x12
257 "urn:", // 0x13
258 "pop:", // 0x14
259 "sip:", // 0x15
260 "sips:", // 0x16
261 "tftp:", // 0x17
262 "btspp://", // 0x18
263 "btl2cap://", // 0x19
264 "btgoep://", // 0x1A
265 "tcpobex://", // 0x1B
266 "irdaobex://", // 0x1C
267 "file://", // 0x1D
268 "urn:epc:id:", // 0x1E
269 "urn:epc:tag:", // 0x1F
270 "urn:epc:pat:", // 0x20
271 "urn:epc:raw:", // 0x21
272 "urn:epc:", // 0x22
Martijn Coenenf180f302013-11-14 13:40:07 -0800273 "urn:nfc:", // 0x23
Nick Pellye0180d02011-06-07 17:27:44 -0700274 };
275
Nick Pellya356bf12011-12-13 15:36:31 -0800276 private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
277
278 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
279
Nick Pelly590b73b2010-10-12 13:00:50 -0700280 private final short mTnf;
281 private final byte[] mType;
282 private final byte[] mId;
283 private final byte[] mPayload;
284
Nick Pellydc993792010-10-04 11:17:25 -0700285 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800286 * Create a new Android Application Record (AAR).
Nick Pellydc993792010-10-04 11:17:25 -0700287 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800288 * This record indicates to other Android devices the package
289 * that should be used to handle the entire NDEF message.
290 * You can embed this record anywhere into your message
291 * to ensure that the intended package receives the message.
292 * <p>
293 * When an Android device dispatches an {@link NdefMessage}
294 * containing one or more Android application records,
295 * the applications contained in those records will be the
296 * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
297 * intent, in the order in which they appear in the message.
298 * This dispatch behavior was first added to Android in
299 * Ice Cream Sandwich.
300 * <p>
301 * If none of the applications have a are installed on the device,
302 * a Market link will be opened to the first application.
303 * <p>
304 * Note that Android application records do not overrule
305 * applications that have called
306 * {@link NfcAdapter#enableForegroundDispatch}.
307 *
308 * @param packageName Android package name
309 * @return Android application NDEF record
310 */
311 public static NdefRecord createApplicationRecord(String packageName) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100312 if (packageName == null) throw new NullPointerException("packageName is null");
313 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
314
Nick Pellya356bf12011-12-13 15:36:31 -0800315 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
Elliott Hughesd396a442013-06-28 16:24:48 -0700316 packageName.getBytes(StandardCharsets.UTF_8));
Nick Pellya356bf12011-12-13 15:36:31 -0800317 }
318
319 /**
320 * Create a new NDEF Record containing a URI.<p>
321 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
322 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
323 * and {@link #RTD_URI}. This is the most efficient encoding
324 * of a URI into NDEF.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100325 * The uri parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400326 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100327 * follow Android best practices for intent filtering.
328 * However the unchecked exception
329 * {@link IllegalArgumentException} may be thrown if the uri
330 * parameter has serious problems, for example if it is empty, so always
331 * catch this exception if you are passing user-generated data into this
332 * method.<p>
333 *
Nick Pellya356bf12011-12-13 15:36:31 -0800334 * Reference specification: NFCForum-TS-RTD_URI_1.0
335 *
336 * @param uri URI to encode.
337 * @return an NDEF Record containing the URI
Nick Pellyc97a5522012-01-05 15:13:01 +1100338 * @throws IllegalArugmentException if the uri is empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800339 */
340 public static NdefRecord createUri(Uri uri) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100341 if (uri == null) throw new NullPointerException("uri is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800342
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400343 uri = uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100344 String uriString = uri.toString();
345 if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
Nick Pellya356bf12011-12-13 15:36:31 -0800346
347 byte prefix = 0;
348 for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
349 if (uriString.startsWith(URI_PREFIX_MAP[i])) {
350 prefix = (byte) i;
351 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
352 break;
353 }
354 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700355 byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800356 byte[] recordBytes = new byte[uriBytes.length + 1];
357 recordBytes[0] = prefix;
358 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
359 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
360 }
361
362 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100363 * Create a new NDEF Record containing a URI.<p>
364 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
365 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
366 * and {@link #RTD_URI}. This is the most efficient encoding
367 * of a URI into NDEF.<p>
368 * The uriString parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400369 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100370 * follow Android best practices for intent filtering.
371 * However the unchecked exception
372 * {@link IllegalArgumentException} may be thrown if the uriString
373 * parameter has serious problems, for example if it is empty, so always
374 * catch this exception if you are passing user-generated data into this
375 * method.<p>
376 *
377 * Reference specification: NFCForum-TS-RTD_URI_1.0
378 *
379 * @param uriString string URI to encode.
380 * @return an NDEF Record containing the URI
381 * @throws IllegalArugmentException if the uriString is empty or invalid
382 */
383 public static NdefRecord createUri(String uriString) {
384 return createUri(Uri.parse(uriString));
385 }
386
387 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800388 * Create a new NDEF Record containing MIME data.<p>
389 * Use this method to encode MIME-typed data into an NDEF Record,
390 * such as "text/plain", or "image/jpeg".<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100391 * The mimeType parameter will be normalized with
392 * {@link Intent#normalizeMimeType} to follow Android best
393 * practices for intent filtering, for example to force lower-case.
394 * However the unchecked exception
395 * {@link IllegalArgumentException} may be thrown
396 * if the mimeType parameter has serious problems,
397 * for example if it is empty, so always catch this
398 * exception if you are passing user-generated data into this method.
399 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800400 * For efficiency, This method might not make an internal copy of the
401 * mimeData byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100402 * to modify the mimeData byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800403 * NdefRecord.
404 *
Nick Pellyc97a5522012-01-05 15:13:01 +1100405 * @param mimeType a valid MIME type
Nick Pellya356bf12011-12-13 15:36:31 -0800406 * @param mimeData MIME data as bytes
407 * @return an NDEF Record containing the MIME-typed data
Nick Pellyc97a5522012-01-05 15:13:01 +1100408 * @throws IllegalArugmentException if the mimeType is empty or invalid
409 *
Nick Pellya356bf12011-12-13 15:36:31 -0800410 */
411 public static NdefRecord createMime(String mimeType, byte[] mimeData) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100412 if (mimeType == null) throw new NullPointerException("mimeType is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800413
Nick Pellyc97a5522012-01-05 15:13:01 +1100414 // We only do basic MIME type validation: trying to follow the
415 // RFCs strictly only ends in tears, since there are lots of MIME
416 // types in common use that are not strictly valid as per RFC rules
417 mimeType = Intent.normalizeMimeType(mimeType);
418 if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
419 int slashIndex = mimeType.indexOf('/');
420 if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
421 if (slashIndex == mimeType.length() - 1) {
422 throw new IllegalArgumentException("mimeType must have minor type");
423 }
424 // missing '/' is allowed
425
426 // MIME RFCs suggest ASCII encoding for content-type
Elliott Hughesd396a442013-06-28 16:24:48 -0700427 byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100428 return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
Nick Pellya356bf12011-12-13 15:36:31 -0800429 }
430
431 /**
432 * Create a new NDEF Record containing external (application-specific) data.<p>
433 * Use this method to encode application specific data into an NDEF Record.
434 * The data is typed by a domain name (usually your Android package name) and
435 * a domain-specific type. This data is packaged into a "NFC Forum External
436 * Type" NDEF Record.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100437 * NFC Forum requires that the domain and type used in an external record
438 * are treated as case insensitive, however Android intent filtering is
439 * always case sensitive. So this method will force the domain and type to
440 * lower-case before creating the NDEF Record.<p>
441 * The unchecked exception {@link IllegalArgumentException} will be thrown
442 * if the domain and type have serious problems, for example if either field
443 * is empty, so always catch this
444 * exception if you are passing user-generated data into this method.<p>
445 * There are no such restrictions on the payload data.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800446 * For efficiency, This method might not make an internal copy of the
447 * data byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100448 * to modify the data byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800449 * NdefRecord.
450 *
451 * Reference specification: NFCForum-TS-RTD_1.0
452 * @param domain domain-name of issuing organization
453 * @param type domain-specific type of data
454 * @param data payload as bytes
Nick Pellyc97a5522012-01-05 15:13:01 +1100455 * @throws IllegalArugmentException if either domain or type are empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800456 */
457 public static NdefRecord createExternal(String domain, String type, byte[] data) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100458 if (domain == null) throw new NullPointerException("domain is null");
459 if (type == null) throw new NullPointerException("type is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800460
Elliott Hughescb64d432013-08-02 10:00:44 -0700461 domain = domain.trim().toLowerCase(Locale.ROOT);
462 type = type.trim().toLowerCase(Locale.ROOT);
Nick Pellyc97a5522012-01-05 15:13:01 +1100463
464 if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
465 if (type.length() == 0) throw new IllegalArgumentException("type is empty");
466
Elliott Hughesd396a442013-06-28 16:24:48 -0700467 byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
468 byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800469 byte[] b = new byte[byteDomain.length + 1 + byteType.length];
470 System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
471 b[byteDomain.length] = ':';
472 System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
473
474 return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
475 }
476
477 /**
Martijn Coenen7fe9fa12014-01-29 17:28:04 -0800478 * Create a new NDEF record containing UTF-8 text data.<p>
479 *
480 * The caller can either specify the language code for the provided text,
481 * or otherwise the language code corresponding to the current default
482 * locale will be used.
483 *
484 * Reference specification: NFCForum-TS-RTD_Text_1.0
485 * @param languageCode The languageCode for the record. If locale is empty or null,
486 * the language code of the current default locale will be used.
487 * @param text The text to be encoded in the record. Will be represented in UTF-8 format.
488 * @throws IllegalArgumentException if text is null
489 */
490 public static NdefRecord createTextRecord(String languageCode, String text) {
491 if (text == null) throw new NullPointerException("text is null");
492
493 byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
494
495 byte[] languageCodeBytes = null;
496 if (languageCode != null && !languageCode.isEmpty()) {
497 languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII);
498 } else {
499 languageCodeBytes = Locale.getDefault().getLanguage().
500 getBytes(StandardCharsets.US_ASCII);
501 }
502 // We only have 6 bits to indicate ISO/IANA language code.
503 if (languageCodeBytes.length >= 64) {
504 throw new IllegalArgumentException("language code is too long, must be <64 bytes.");
505 }
506 ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
507
508 byte status = (byte) (languageCodeBytes.length & 0xFF);
509 buffer.put(status);
510 buffer.put(languageCodeBytes);
511 buffer.put(textBytes);
512
513 return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array());
514 }
515
516 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800517 * Construct an NDEF Record from its component fields.<p>
518 * Recommend to use helpers such as {#createUri} or
519 * {{@link #createExternal} where possible, since they perform
520 * stricter validation that the record is correctly formatted
521 * as per NDEF specifications. However if you know what you are
522 * doing then this constructor offers the most flexibility.<p>
523 * An {@link NdefRecord} represents a logical (complete)
524 * record, and cannot represent NDEF Record chunks.<p>
525 * Basic validation of the tnf, type, id and payload is performed
526 * as per the following rules:
527 * <ul>
528 * <li>The tnf paramter must be a 3-bit value.</li>
529 * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
530 * id or payload.</li>
531 * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
532 * cannot have a type.</li>
533 * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
534 * since this class only represents complete (unchunked) records.</li>
535 * </ul>
536 * This minimal validation is specified by
537 * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
538 * If any of the above validation
539 * steps fail then {@link IllegalArgumentException} is thrown.<p>
540 * Deep inspection of the type, id and payload fields is not
541 * performed, so it is possible to create NDEF Records
542 * that conform to section 3.2.6
543 * but fail other more strict NDEF specification requirements. For
544 * example, the payload may be invalid given the tnf and type.
545 * <p>
546 * To omit a type, id or payload field, set the parameter to an
547 * empty byte array or null.
Nick Pellydc993792010-10-04 11:17:25 -0700548 *
549 * @param tnf a 3-bit TNF constant
Nick Pellya356bf12011-12-13 15:36:31 -0800550 * @param type byte array, containing zero to 255 bytes, or null
551 * @param id byte array, containing zero to 255 bytes, or null
Nick Pellydc993792010-10-04 11:17:25 -0700552 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
Nick Pellya356bf12011-12-13 15:36:31 -0800553 * or null
554 * @throws IllegalArugmentException if a valid record cannot be created
Nick Pellydc993792010-10-04 11:17:25 -0700555 */
556 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
Nick Pellya356bf12011-12-13 15:36:31 -0800557 /* convert nulls */
558 if (type == null) type = EMPTY_BYTE_ARRAY;
559 if (id == null) id = EMPTY_BYTE_ARRAY;
560 if (payload == null) payload = EMPTY_BYTE_ARRAY;
Martijn Coenen8bede172011-04-08 16:42:22 +0200561
Nick Pellya356bf12011-12-13 15:36:31 -0800562 String message = validateTnf(tnf, type, id, payload);
563 if (message != null) {
564 throw new IllegalArgumentException(message);
Nick Pelly590b73b2010-10-12 13:00:50 -0700565 }
566
Nick Pelly590b73b2010-10-12 13:00:50 -0700567 mTnf = tnf;
Nick Pellya356bf12011-12-13 15:36:31 -0800568 mType = type;
569 mId = id;
570 mPayload = payload;
Nick Pellydc993792010-10-04 11:17:25 -0700571 }
572
573 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800574 * Construct an NDEF Record from raw bytes.<p>
575 * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
576 * instead. This is because it does not make sense to parse a record:
577 * the NDEF binary format is only defined for a message, and the
578 * record flags MB and ME do not make sense outside of the context of
579 * an entire message.<p>
580 * This implementation will attempt to parse a single record by ignoring
581 * the MB and ME flags, and otherwise following the rules of
582 * {@link NdefMessage#NdefMessage(byte[])}.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700583 *
Nick Pellya356bf12011-12-13 15:36:31 -0800584 * @param data raw bytes to parse
585 * @throws FormatException if the data cannot be parsed into a valid record
586 * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
Nick Pellydc993792010-10-04 11:17:25 -0700587 */
Nick Pellya356bf12011-12-13 15:36:31 -0800588 @Deprecated
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700589 public NdefRecord(byte[] data) throws FormatException {
Nick Pellya356bf12011-12-13 15:36:31 -0800590 ByteBuffer buffer = ByteBuffer.wrap(data);
591 NdefRecord[] rs = parse(buffer, true);
592
593 if (buffer.remaining() > 0) {
594 throw new FormatException("data too long");
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700595 }
Nick Pellya356bf12011-12-13 15:36:31 -0800596
597 mTnf = rs[0].mTnf;
598 mType = rs[0].mType;
599 mId = rs[0].mId;
600 mPayload = rs[0].mPayload;
Nick Pellydc993792010-10-04 11:17:25 -0700601 }
602
603 /**
604 * Returns the 3-bit TNF.
605 * <p>
606 * TNF is the top-level type.
607 */
608 public short getTnf() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700609 return mTnf;
Nick Pellydc993792010-10-04 11:17:25 -0700610 }
611
612 /**
613 * Returns the variable length Type field.
614 * <p>
615 * This should be used in conjunction with the TNF field to determine the
616 * payload format.
Nick Pellya356bf12011-12-13 15:36:31 -0800617 * <p>
618 * Returns an empty byte array if this record
619 * does not have a type field.
Nick Pellydc993792010-10-04 11:17:25 -0700620 */
621 public byte[] getType() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700622 return mType.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700623 }
624
625 /**
626 * Returns the variable length ID.
Nick Pellya356bf12011-12-13 15:36:31 -0800627 * <p>
628 * Returns an empty byte array if this record
629 * does not have an id field.
Nick Pellydc993792010-10-04 11:17:25 -0700630 */
631 public byte[] getId() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700632 return mId.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700633 }
634
635 /**
636 * Returns the variable length payload.
Nick Pellya356bf12011-12-13 15:36:31 -0800637 * <p>
638 * Returns an empty byte array if this record
639 * does not have a payload field.
Nick Pellydc993792010-10-04 11:17:25 -0700640 */
641 public byte[] getPayload() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700642 return mPayload.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700643 }
644
645 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800646 * Return this NDEF Record as a byte array.<p>
647 * This method is deprecated, use {@link NdefMessage#toByteArray}
648 * instead. This is because the NDEF binary format is not defined for
649 * a record outside of the context of a message: the MB and ME flags
650 * cannot be set without knowing the location inside a message.<p>
651 * This implementation will attempt to serialize a single record by
652 * always setting the MB and ME flags (in other words, assume this
653 * is a single-record NDEF Message).<p>
654 *
655 * @deprecated use {@link NdefMessage#toByteArray()} instead
656 */
657 @Deprecated
658 public byte[] toByteArray() {
659 ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
660 writeToByteBuffer(buffer, true, true);
661 return buffer.array();
662 }
663
664 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100665 * Map this record to a MIME type, or return null if it cannot be mapped.<p>
666 * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
667 * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
668 * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
669 * is returned, otherwise null is returned.<p>
670 * This method does not perform validation that the MIME type is
671 * actually valid. It always attempts to
672 * return a string containing the type if this is a MIME record.<p>
673 * The returned MIME type will by normalized to lower-case using
674 * {@link Intent#normalizeMimeType}.<p>
675 * The MIME payload can be obtained using {@link #getPayload}.
676 *
677 * @return MIME type as a string, or null if this is not a MIME record
Nick Pellye0180d02011-06-07 17:27:44 -0700678 */
Nick Pellyc97a5522012-01-05 15:13:01 +1100679 public String toMimeType() {
680 switch (mTnf) {
681 case NdefRecord.TNF_WELL_KNOWN:
682 if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
683 return "text/plain";
684 }
685 break;
686 case NdefRecord.TNF_MIME_MEDIA:
Elliott Hughesd396a442013-06-28 16:24:48 -0700687 String mimeType = new String(mType, StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100688 return Intent.normalizeMimeType(mimeType);
Nick Pellye0180d02011-06-07 17:27:44 -0700689 }
Nick Pellyc97a5522012-01-05 15:13:01 +1100690 return null;
Nick Pellye0180d02011-06-07 17:27:44 -0700691 }
692
693 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100694 * Map this record to a URI, or return null if it cannot be mapped.<p>
695 * Currently this method considers the following to be URI records:
696 * <ul>
697 * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
698 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
699 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
700 * and containing a URI record in the NDEF message nested in the payload.
701 * </li>
702 * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
703 * </ul>
704 * If this is not a URI record by the above rules, then null is returned.<p>
705 * This method does not perform validation that the URI is
706 * actually valid: it always attempts to create and return a URI if
707 * this record appears to be a URI record by the above rules.<p>
708 * The returned URI will be normalized to have a lower case scheme
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400709 * using {@link Uri#normalizeScheme}.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100710 *
711 * @return URI, or null if this is not a URI record
712 */
713 public Uri toUri() {
714 return toUri(false);
715 }
716
717 private Uri toUri(boolean inSmartPoster) {
718 switch (mTnf) {
719 case TNF_WELL_KNOWN:
720 if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
721 try {
722 // check payload for a nested NDEF Message containing a URI
723 NdefMessage nestedMessage = new NdefMessage(mPayload);
724 for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
725 Uri uri = nestedRecord.toUri(true);
726 if (uri != null) {
727 return uri;
728 }
729 }
730 } catch (FormatException e) { }
731 } else if (Arrays.equals(mType, RTD_URI)) {
Martijn Coenen32ac1e12012-09-06 17:09:06 +0200732 Uri wktUri = parseWktUri();
733 return (wktUri != null ? wktUri.normalizeScheme() : null);
Nick Pellyc97a5522012-01-05 15:13:01 +1100734 }
735 break;
736
737 case TNF_ABSOLUTE_URI:
Elliott Hughesd396a442013-06-28 16:24:48 -0700738 Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400739 return uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100740
741 case TNF_EXTERNAL_TYPE:
742 if (inSmartPoster) {
743 break;
744 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700745 return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
Nick Pellyc97a5522012-01-05 15:13:01 +1100746 }
747 return null;
748 }
749
750 /**
751 * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
752 * @return complete URI, or null if invalid
753 */
754 private Uri parseWktUri() {
755 if (mPayload.length < 2) {
756 return null;
757 }
758
759 // payload[0] contains the URI Identifier Code, as per
760 // NFC Forum "URI Record Type Definition" section 3.2.2.
761 int prefixIndex = (mPayload[0] & (byte)0xFF);
762 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
763 return null;
764 }
765 String prefix = URI_PREFIX_MAP[prefixIndex];
766 String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
Elliott Hughesd396a442013-06-28 16:24:48 -0700767 StandardCharsets.UTF_8);
Nick Pellyc97a5522012-01-05 15:13:01 +1100768 return Uri.parse(prefix + suffix);
769 }
770
771 /**
772 * Main record parsing method.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800773 * Expects NdefMessage to begin immediately, allows trailing data.<p>
774 * Currently has strict validation of all fields as per NDEF 1.0
775 * specification section 2.5. We will attempt to keep this as strict as
776 * possible to encourage well-formatted NDEF.<p>
777 * Always returns 1 or more NdefRecord's, or throws FormatException.
778 *
779 * @param buffer ByteBuffer to read from
780 * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
781 * @return one or more records
782 * @throws FormatException on any parsing error
Nick Pellydc993792010-10-04 11:17:25 -0700783 */
Nick Pellya356bf12011-12-13 15:36:31 -0800784 static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
785 List<NdefRecord> records = new ArrayList<NdefRecord>();
786
787 try {
788 byte[] type = null;
789 byte[] id = null;
790 byte[] payload = null;
791 ArrayList<byte[]> chunks = new ArrayList<byte[]>();
792 boolean inChunk = false;
793 short chunkTnf = -1;
794 boolean me = false;
795
796 while (!me) {
797 byte flag = buffer.get();
798
799 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
800 me = (flag & NdefRecord.FLAG_ME) != 0;
801 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
802 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
803 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
804 short tnf = (short)(flag & 0x07);
805
806 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
807 throw new FormatException("expected MB flag");
808 } else if (mb && records.size() != 0 && !ignoreMbMe) {
809 throw new FormatException("unexpected MB flag");
810 } else if (inChunk && il) {
811 throw new FormatException("unexpected IL flag in non-leading chunk");
812 } else if (cf && me) {
813 throw new FormatException("unexpected ME flag in non-trailing chunk");
814 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
815 throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
816 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
817 throw new FormatException("" +
John Spurlock8a985d22014-02-25 09:40:05 -0500818 "unexpected TNF_UNCHANGED in first chunk or unchunked record");
Nick Pellya356bf12011-12-13 15:36:31 -0800819 }
820
821 int typeLength = buffer.get() & 0xFF;
822 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
823 int idLength = il ? (buffer.get() & 0xFF) : 0;
824
825 if (inChunk && typeLength != 0) {
826 throw new FormatException("expected zero-length type in non-leading chunk");
827 }
828
829 if (!inChunk) {
830 type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
831 id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
832 buffer.get(type);
833 buffer.get(id);
834 }
835
836 ensureSanePayloadSize(payloadLength);
837 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
838 buffer.get(payload);
839
840 if (cf && !inChunk) {
841 // first chunk
842 chunks.clear();
843 chunkTnf = tnf;
844 }
845 if (cf || inChunk) {
846 // any chunk
847 chunks.add(payload);
848 }
849 if (!cf && inChunk) {
850 // last chunk, flatten the payload
851 payloadLength = 0;
852 for (byte[] p : chunks) {
853 payloadLength += p.length;
854 }
855 ensureSanePayloadSize(payloadLength);
856 payload = new byte[(int)payloadLength];
857 int i = 0;
858 for (byte[] p : chunks) {
859 System.arraycopy(p, 0, payload, i, p.length);
860 i += p.length;
861 }
862 tnf = chunkTnf;
863 }
864 if (cf) {
865 // more chunks to come
866 inChunk = true;
867 continue;
868 } else {
869 inChunk = false;
870 }
871
872 String error = validateTnf(tnf, type, id, payload);
873 if (error != null) {
874 throw new FormatException(error);
875 }
876 records.add(new NdefRecord(tnf, type, id, payload));
877 if (ignoreMbMe) { // for parsing a single NdefRecord
878 break;
879 }
880 }
881 } catch (BufferUnderflowException e) {
882 throw new FormatException("expected more data", e);
883 }
884 return records.toArray(new NdefRecord[records.size()]);
Nick Pellydc993792010-10-04 11:17:25 -0700885 }
886
Nick Pellya356bf12011-12-13 15:36:31 -0800887 private static void ensureSanePayloadSize(long size) throws FormatException {
888 if (size > MAX_PAYLOAD_SIZE) {
889 throw new FormatException(
890 "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
891 }
892 }
893
894 /**
895 * Perform simple validation that the tnf is valid.<p>
896 * Validates the requirements of NFCForum-TS-NDEF_1.0 section
897 * 3.2.6 (Type Name Format). This just validates that the tnf
898 * is valid, and that the relevant type, id and payload
899 * fields are present (or empty) for this tnf. It does not
900 * perform any deep inspection of the type, id and payload fields.<p>
901 * Also does not allow TNF_UNCHANGED since this class is only used
902 * to present logical (unchunked) records.
903 *
904 * @return null if valid, or a string error if invalid.
905 */
906 static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
907 switch (tnf) {
908 case TNF_EMPTY:
909 if (type.length != 0 || id.length != 0 || payload.length != 0) {
910 return "unexpected data in TNF_EMPTY record";
911 }
912 return null;
913 case TNF_WELL_KNOWN:
914 case TNF_MIME_MEDIA:
915 case TNF_ABSOLUTE_URI:
916 case TNF_EXTERNAL_TYPE:
917 return null;
918 case TNF_UNKNOWN:
919 case TNF_RESERVED:
920 if (type.length != 0) {
921 return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
922 }
923 return null;
924 case TNF_UNCHANGED:
925 return "unexpected TNF_UNCHANGED in first chunk or logical record";
926 default:
927 return String.format("unexpected tnf value: 0x%02x", tnf);
928 }
929 }
930
931 /**
932 * Serialize record for network transmission.<p>
933 * Uses specified MB and ME flags.<p>
934 * Does not chunk records.
935 */
936 void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
937 boolean sr = mPayload.length < 256;
938 boolean il = mId.length > 0;
939
940 byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
941 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
942 buffer.put(flags);
943
944 buffer.put((byte)mType.length);
945 if (sr) {
946 buffer.put((byte)mPayload.length);
947 } else {
948 buffer.putInt(mPayload.length);
949 }
950 if (il) {
951 buffer.put((byte)mId.length);
952 }
953
954 buffer.put(mType);
955 buffer.put(mId);
956 buffer.put(mPayload);
957 }
958
959 /**
960 * Get byte length of serialized record.
961 */
962 int getByteLength() {
963 int length = 3 + mType.length + mId.length + mPayload.length;
964
965 boolean sr = mPayload.length < 256;
966 boolean il = mId.length > 0;
967
968 if (!sr) length += 3;
969 if (il) length += 1;
970
971 return length;
972 }
973
974 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700975 public int describeContents() {
976 return 0;
977 }
978
Nick Pellya356bf12011-12-13 15:36:31 -0800979 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700980 public void writeToParcel(Parcel dest, int flags) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700981 dest.writeInt(mTnf);
982 dest.writeInt(mType.length);
983 dest.writeByteArray(mType);
984 dest.writeInt(mId.length);
985 dest.writeByteArray(mId);
986 dest.writeInt(mPayload.length);
987 dest.writeByteArray(mPayload);
Nick Pellydc993792010-10-04 11:17:25 -0700988 }
989
990 public static final Parcelable.Creator<NdefRecord> CREATOR =
991 new Parcelable.Creator<NdefRecord>() {
Nick Pellya356bf12011-12-13 15:36:31 -0800992 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700993 public NdefRecord createFromParcel(Parcel in) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700994 short tnf = (short)in.readInt();
995 int typeLength = in.readInt();
996 byte[] type = new byte[typeLength];
997 in.readByteArray(type);
998 int idLength = in.readInt();
999 byte[] id = new byte[idLength];
1000 in.readByteArray(id);
1001 int payloadLength = in.readInt();
1002 byte[] payload = new byte[payloadLength];
1003 in.readByteArray(payload);
1004
Nick Pellya356bf12011-12-13 15:36:31 -08001005 return new NdefRecord(tnf, type, id, payload);
Nick Pellydc993792010-10-04 11:17:25 -07001006 }
Nick Pellya356bf12011-12-13 15:36:31 -08001007 @Override
Nick Pellydc993792010-10-04 11:17:25 -07001008 public NdefRecord[] newArray(int size) {
Nick Pelly590b73b2010-10-12 13:00:50 -07001009 return new NdefRecord[size];
Nick Pellydc993792010-10-04 11:17:25 -07001010 }
1011 };
Nick Pelly590b73b2010-10-12 13:00:50 -07001012
Nick Pellya356bf12011-12-13 15:36:31 -08001013 @Override
1014 public int hashCode() {
1015 final int prime = 31;
1016 int result = 1;
1017 result = prime * result + Arrays.hashCode(mId);
1018 result = prime * result + Arrays.hashCode(mPayload);
1019 result = prime * result + mTnf;
1020 result = prime * result + Arrays.hashCode(mType);
1021 return result;
1022 }
1023
1024 /**
1025 * Returns true if the specified NDEF Record contains
1026 * identical tnf, type, id and payload fields.
1027 */
1028 @Override
1029 public boolean equals(Object obj) {
1030 if (this == obj) return true;
1031 if (obj == null) return false;
1032 if (getClass() != obj.getClass()) return false;
1033 NdefRecord other = (NdefRecord) obj;
1034 if (!Arrays.equals(mId, other.mId)) return false;
1035 if (!Arrays.equals(mPayload, other.mPayload)) return false;
1036 if (mTnf != other.mTnf) return false;
1037 return Arrays.equals(mType, other.mType);
1038 }
1039
1040 @Override
1041 public String toString() {
1042 StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
1043 if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
1044 if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
1045 if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
1046 return b.toString();
1047 }
1048
1049 private static StringBuilder bytesToString(byte[] bs) {
1050 StringBuilder s = new StringBuilder();
1051 for (byte b : bs) {
1052 s.append(String.format("%02X", b));
1053 }
1054 return s;
1055 }
Martijn Coenen8bede172011-04-08 16:42:22 +02001056}