blob: 74f8cb4c1d3dac91fa85ebe905f773dfa8e71951 [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
Artur Satayevafdb23a2019-12-10 17:47:53 +000019import android.compat.annotation.UnsupportedAppUsage;
Nick Pellyc97a5522012-01-05 15:13:01 +110020import android.content.Intent;
Nick Pellye0180d02011-06-07 17:27:44 -070021import android.net.Uri;
Nick Pellydc993792010-10-04 11:17:25 -070022import android.os.Parcel;
23import android.os.Parcelable;
Mike Ma5313cbc2020-01-22 13:28:16 -080024import android.util.proto.ProtoOutputStream;
Martijn Coenen7fe9fa12014-01-29 17:28:04 -080025
Nick Pellya356bf12011-12-13 15:36:31 -080026import java.nio.BufferUnderflowException;
27import java.nio.ByteBuffer;
Elliott Hughesd396a442013-06-28 16:24:48 -070028import java.nio.charset.StandardCharsets;
Nick Pellya356bf12011-12-13 15:36:31 -080029import java.util.ArrayList;
Nick Pellye0180d02011-06-07 17:27:44 -070030import java.util.Arrays;
Nick Pellya356bf12011-12-13 15:36:31 -080031import java.util.List;
Nick Pellyc97a5522012-01-05 15:13:01 +110032import java.util.Locale;
Nick Pellydc993792010-10-04 11:17:25 -070033
34/**
Nick Pellya356bf12011-12-13 15:36:31 -080035 * Represents an immutable NDEF Record.
36 * <p>
37 * NDEF (NFC Data Exchange Format) is a light-weight binary format,
38 * used to encapsulate typed data. It is specified by the NFC Forum,
39 * for transmission and storage with NFC, however it is transport agnostic.
40 * <p>
41 * NDEF defines messages and records. An NDEF Record contains
42 * typed data, such as MIME-type media, a URI, or a custom
43 * application payload. An NDEF Message is a container for
44 * one or more NDEF Records.
45 * <p>
46 * This class represents logical (complete) NDEF Records, and can not be
47 * used to represent chunked (partial) NDEF Records. However
48 * {@link NdefMessage#NdefMessage(byte[])} can be used to parse a message
49 * containing chunked records, and will return a message with unchunked
50 * (complete) records.
51 * <p>
52 * A logical NDEF Record always contains a 3-bit TNF (Type Name Field)
53 * that provides high level typing for the rest of the record. The
54 * remaining fields are variable length and not always present:
Nick Pellydc993792010-10-04 11:17:25 -070055 * <ul>
Nick Pellya356bf12011-12-13 15:36:31 -080056 * <li><em>type</em>: detailed typing for the payload</li>
57 * <li><em>id</em>: identifier meta-data, not commonly used</li>
58 * <li><em>payload</em>: the actual payload</li>
Nick Pellydc993792010-10-04 11:17:25 -070059 * </ul>
Nick Pellya356bf12011-12-13 15:36:31 -080060 * <p>
61 * Helpers such as {@link NdefRecord#createUri}, {@link NdefRecord#createMime}
62 * and {@link NdefRecord#createExternal} are included to create well-formatted
63 * NDEF Records with correctly set tnf, type, id and payload fields, please
64 * use these helpers whenever possible.
65 * <p>
66 * Use the constructor {@link #NdefRecord(short, byte[], byte[], byte[])}
67 * if you know what you are doing and what to set the fields individually.
68 * Only basic validation is performed with this constructor, so it is possible
69 * to create records that do not confirm to the strict NFC Forum
70 * specifications.
71 * <p>
72 * The binary representation of an NDEF Record includes additional flags to
73 * indicate location with an NDEF message, provide support for chunking of
74 * NDEF records, and to pack optional fields. This class does not expose
75 * those details. To write an NDEF Record as binary you must first put it
Jeff Smitha45746e2012-07-19 14:19:24 -050076 * into an {@link NdefMessage}, then call {@link NdefMessage#toByteArray()}.
Nick Pellya356bf12011-12-13 15:36:31 -080077 * <p class="note">
78 * {@link NdefMessage} and {@link NdefRecord} implementations are
79 * always available, even on Android devices that do not have NFC hardware.
80 * <p class="note">
81 * {@link NdefRecord}s are intended to be immutable (and thread-safe),
82 * however they may contain mutable fields. So take care not to modify
83 * mutable fields passed into constructors, or modify mutable fields
84 * obtained by getter methods, unless such modification is explicitly
85 * marked as safe.
86 *
87 * @see NfcAdapter#ACTION_NDEF_DISCOVERED
88 * @see NdefMessage
Nick Pellydc993792010-10-04 11:17:25 -070089 */
Nick Pelly11b075e2010-10-28 13:39:37 -070090public final class NdefRecord implements Parcelable {
Nick Pellydc993792010-10-04 11:17:25 -070091 /**
Nick Pellya356bf12011-12-13 15:36:31 -080092 * Indicates the record is empty.<p>
93 * Type, id and payload fields are empty in a {@literal TNF_EMPTY} record.
Nick Pellydc993792010-10-04 11:17:25 -070094 */
95 public static final short TNF_EMPTY = 0x00;
96
97 /**
Nick Pellya356bf12011-12-13 15:36:31 -080098 * Indicates the type field contains a well-known RTD type name.<p>
99 * Use this tnf with RTD types such as {@link #RTD_TEXT}, {@link #RTD_URI}.
Nick Pellydc993792010-10-04 11:17:25 -0700100 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800101 * The RTD type name format is specified in NFCForum-TS-RTD_1.0.
102 *
103 * @see #RTD_URI
104 * @see #RTD_TEXT
105 * @see #RTD_SMART_POSTER
106 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700107 */
108 public static final short TNF_WELL_KNOWN = 0x01;
109
110 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800111 * Indicates the type field contains a media-type BNF
112 * construct, defined by RFC 2046.<p>
113 * Use this with MIME type names such as {@literal "image/jpeg"}, or
114 * using the helper {@link #createMime}.
115 *
116 * @see #createMime
Nick Pellydc993792010-10-04 11:17:25 -0700117 */
118 public static final short TNF_MIME_MEDIA = 0x02;
119
120 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800121 * Indicates the type field contains an absolute-URI
122 * BNF construct defined by RFC 3986.<p>
123 * When creating new records prefer {@link #createUri},
124 * since it offers more compact URI encoding
125 * ({@literal #RTD_URI} allows compression of common URI prefixes).
126 *
127 * @see #createUri
Nick Pellydc993792010-10-04 11:17:25 -0700128 */
129 public static final short TNF_ABSOLUTE_URI = 0x03;
130
131 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800132 * Indicates the type field contains an external type name.<p>
133 * Used to encode custom payloads. When creating new records
134 * use the helper {@link #createExternal}.<p>
135 * The external-type RTD format is specified in NFCForum-TS-RTD_1.0.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700136 * <p>
137 * Note this TNF should not be used with RTD_TEXT or RTD_URI constants.
138 * Those are well known RTD constants, not external RTD constants.
Nick Pellya356bf12011-12-13 15:36:31 -0800139 *
140 * @see #createExternal
Nick Pellydc993792010-10-04 11:17:25 -0700141 */
142 public static final short TNF_EXTERNAL_TYPE = 0x04;
143
144 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800145 * Indicates the payload type is unknown.<p>
146 * NFC Forum explains this should be treated similarly to the
147 * "application/octet-stream" MIME type. The payload
148 * type is not explicitly encoded within the record.
Nick Pellydc993792010-10-04 11:17:25 -0700149 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800150 * The type field is empty in an {@literal TNF_UNKNOWN} record.
Nick Pellydc993792010-10-04 11:17:25 -0700151 */
152 public static final short TNF_UNKNOWN = 0x05;
153
154 /**
155 * Indicates the payload is an intermediate or final chunk of a chunked
Nick Pellya356bf12011-12-13 15:36:31 -0800156 * NDEF Record.<p>
157 * {@literal TNF_UNCHANGED} can not be used with this class
158 * since all {@link NdefRecord}s are already unchunked, however they
159 * may appear in the binary format.
Nick Pellydc993792010-10-04 11:17:25 -0700160 */
161 public static final short TNF_UNCHANGED = 0x06;
162
163 /**
164 * Reserved TNF type.
165 * <p>
166 * The NFC Forum NDEF Specification v1.0 suggests for NDEF parsers to treat this
167 * value like TNF_UNKNOWN.
168 * @hide
169 */
170 public static final short TNF_RESERVED = 0x07;
171
172 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800173 * RTD Text type. For use with {@literal TNF_WELL_KNOWN}.
174 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700175 */
176 public static final byte[] RTD_TEXT = {0x54}; // "T"
177
178 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800179 * RTD URI type. For use with {@literal TNF_WELL_KNOWN}.
180 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700181 */
182 public static final byte[] RTD_URI = {0x55}; // "U"
183
184 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800185 * RTD Smart Poster type. For use with {@literal TNF_WELL_KNOWN}.
186 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700187 */
188 public static final byte[] RTD_SMART_POSTER = {0x53, 0x70}; // "Sp"
189
190 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800191 * RTD Alternative Carrier type. For use with {@literal TNF_WELL_KNOWN}.
192 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700193 */
194 public static final byte[] RTD_ALTERNATIVE_CARRIER = {0x61, 0x63}; // "ac"
195
196 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800197 * RTD Handover Carrier type. For use with {@literal TNF_WELL_KNOWN}.
198 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700199 */
200 public static final byte[] RTD_HANDOVER_CARRIER = {0x48, 0x63}; // "Hc"
201
202 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800203 * RTD Handover Request type. For use with {@literal TNF_WELL_KNOWN}.
204 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700205 */
206 public static final byte[] RTD_HANDOVER_REQUEST = {0x48, 0x72}; // "Hr"
207
208 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800209 * RTD Handover Select type. For use with {@literal TNF_WELL_KNOWN}.
210 * @see #TNF_WELL_KNOWN
Nick Pellydc993792010-10-04 11:17:25 -0700211 */
212 public static final byte[] RTD_HANDOVER_SELECT = {0x48, 0x73}; // "Hs"
213
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700214 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800215 * RTD Android app type. For use with {@literal TNF_EXTERNAL}.
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700216 * <p>
217 * The payload of a record with type RTD_ANDROID_APP
218 * should be the package name identifying an application.
219 * Multiple RTD_ANDROID_APP records may be included
220 * in a single {@link NdefMessage}.
221 * <p>
222 * Use {@link #createApplicationRecord(String)} to create
223 * RTD_ANDROID_APP records.
224 * @hide
225 */
Martijn Coenena37fcbc2011-08-05 09:56:17 -0700226 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();
227
Nick Pelly590b73b2010-10-12 13:00:50 -0700228 private static final byte FLAG_MB = (byte) 0x80;
229 private static final byte FLAG_ME = (byte) 0x40;
230 private static final byte FLAG_CF = (byte) 0x20;
231 private static final byte FLAG_SR = (byte) 0x10;
232 private static final byte FLAG_IL = (byte) 0x08;
233
Nick Pellye0180d02011-06-07 17:27:44 -0700234 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800235 * NFC Forum "URI Record Type Definition"<p>
Nick Pellye0180d02011-06-07 17:27:44 -0700236 * This is a mapping of "URI Identifier Codes" to URI string prefixes,
237 * per section 3.2.2 of the NFC Forum URI Record Type Definition document.
238 */
239 private static final String[] URI_PREFIX_MAP = new String[] {
240 "", // 0x00
241 "http://www.", // 0x01
242 "https://www.", // 0x02
243 "http://", // 0x03
244 "https://", // 0x04
245 "tel:", // 0x05
246 "mailto:", // 0x06
247 "ftp://anonymous:anonymous@", // 0x07
248 "ftp://ftp.", // 0x08
249 "ftps://", // 0x09
250 "sftp://", // 0x0A
251 "smb://", // 0x0B
252 "nfs://", // 0x0C
253 "ftp://", // 0x0D
254 "dav://", // 0x0E
255 "news:", // 0x0F
256 "telnet://", // 0x10
257 "imap:", // 0x11
258 "rtsp://", // 0x12
259 "urn:", // 0x13
260 "pop:", // 0x14
261 "sip:", // 0x15
262 "sips:", // 0x16
263 "tftp:", // 0x17
264 "btspp://", // 0x18
265 "btl2cap://", // 0x19
266 "btgoep://", // 0x1A
267 "tcpobex://", // 0x1B
268 "irdaobex://", // 0x1C
269 "file://", // 0x1D
270 "urn:epc:id:", // 0x1E
271 "urn:epc:tag:", // 0x1F
272 "urn:epc:pat:", // 0x20
273 "urn:epc:raw:", // 0x21
274 "urn:epc:", // 0x22
Martijn Coenenf180f302013-11-14 13:40:07 -0800275 "urn:nfc:", // 0x23
Nick Pellye0180d02011-06-07 17:27:44 -0700276 };
277
Nick Pellya356bf12011-12-13 15:36:31 -0800278 private static final int MAX_PAYLOAD_SIZE = 10 * (1 << 20); // 10 MB payload limit
279
280 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
281
Nick Pelly590b73b2010-10-12 13:00:50 -0700282 private final short mTnf;
283 private final byte[] mType;
Mathew Inwood57069dc2018-07-31 15:33:20 +0100284 @UnsupportedAppUsage
Nick Pelly590b73b2010-10-12 13:00:50 -0700285 private final byte[] mId;
286 private final byte[] mPayload;
287
Nick Pellydc993792010-10-04 11:17:25 -0700288 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800289 * Create a new Android Application Record (AAR).
Nick Pellydc993792010-10-04 11:17:25 -0700290 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800291 * This record indicates to other Android devices the package
292 * that should be used to handle the entire NDEF message.
293 * You can embed this record anywhere into your message
294 * to ensure that the intended package receives the message.
295 * <p>
296 * When an Android device dispatches an {@link NdefMessage}
297 * containing one or more Android application records,
298 * the applications contained in those records will be the
299 * preferred target for the {@link NfcAdapter#ACTION_NDEF_DISCOVERED}
300 * intent, in the order in which they appear in the message.
301 * This dispatch behavior was first added to Android in
302 * Ice Cream Sandwich.
303 * <p>
304 * If none of the applications have a are installed on the device,
305 * a Market link will be opened to the first application.
306 * <p>
307 * Note that Android application records do not overrule
308 * applications that have called
309 * {@link NfcAdapter#enableForegroundDispatch}.
310 *
311 * @param packageName Android package name
312 * @return Android application NDEF record
313 */
314 public static NdefRecord createApplicationRecord(String packageName) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100315 if (packageName == null) throw new NullPointerException("packageName is null");
316 if (packageName.length() == 0) throw new IllegalArgumentException("packageName is empty");
317
Nick Pellya356bf12011-12-13 15:36:31 -0800318 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, null,
Elliott Hughesd396a442013-06-28 16:24:48 -0700319 packageName.getBytes(StandardCharsets.UTF_8));
Nick Pellya356bf12011-12-13 15:36:31 -0800320 }
321
322 /**
323 * Create a new NDEF Record containing a URI.<p>
324 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
325 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
326 * and {@link #RTD_URI}. This is the most efficient encoding
327 * of a URI into NDEF.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100328 * The uri parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400329 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100330 * follow Android best practices for intent filtering.
331 * However the unchecked exception
332 * {@link IllegalArgumentException} may be thrown if the uri
333 * parameter has serious problems, for example if it is empty, so always
334 * catch this exception if you are passing user-generated data into this
335 * method.<p>
336 *
Nick Pellya356bf12011-12-13 15:36:31 -0800337 * Reference specification: NFCForum-TS-RTD_URI_1.0
338 *
339 * @param uri URI to encode.
340 * @return an NDEF Record containing the URI
Nick Pellyc97a5522012-01-05 15:13:01 +1100341 * @throws IllegalArugmentException if the uri is empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800342 */
343 public static NdefRecord createUri(Uri uri) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100344 if (uri == null) throw new NullPointerException("uri is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800345
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400346 uri = uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100347 String uriString = uri.toString();
348 if (uriString.length() == 0) throw new IllegalArgumentException("uri is empty");
Nick Pellya356bf12011-12-13 15:36:31 -0800349
350 byte prefix = 0;
351 for (int i = 1; i < URI_PREFIX_MAP.length; i++) {
352 if (uriString.startsWith(URI_PREFIX_MAP[i])) {
353 prefix = (byte) i;
354 uriString = uriString.substring(URI_PREFIX_MAP[i].length());
355 break;
356 }
357 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700358 byte[] uriBytes = uriString.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800359 byte[] recordBytes = new byte[uriBytes.length + 1];
360 recordBytes[0] = prefix;
361 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length);
362 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, null, recordBytes);
363 }
364
365 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100366 * Create a new NDEF Record containing a URI.<p>
367 * Use this method to encode a URI (or URL) into an NDEF Record.<p>
368 * Uses the well known URI type representation: {@link #TNF_WELL_KNOWN}
369 * and {@link #RTD_URI}. This is the most efficient encoding
370 * of a URI into NDEF.<p>
371 * The uriString parameter will be normalized with
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400372 * {@link Uri#normalizeScheme} to set the scheme to lower case to
Nick Pellyc97a5522012-01-05 15:13:01 +1100373 * follow Android best practices for intent filtering.
374 * However the unchecked exception
375 * {@link IllegalArgumentException} may be thrown if the uriString
376 * parameter has serious problems, for example if it is empty, so always
377 * catch this exception if you are passing user-generated data into this
378 * method.<p>
379 *
380 * Reference specification: NFCForum-TS-RTD_URI_1.0
381 *
382 * @param uriString string URI to encode.
383 * @return an NDEF Record containing the URI
384 * @throws IllegalArugmentException if the uriString is empty or invalid
385 */
386 public static NdefRecord createUri(String uriString) {
387 return createUri(Uri.parse(uriString));
388 }
389
390 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800391 * Create a new NDEF Record containing MIME data.<p>
392 * Use this method to encode MIME-typed data into an NDEF Record,
393 * such as "text/plain", or "image/jpeg".<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100394 * The mimeType parameter will be normalized with
395 * {@link Intent#normalizeMimeType} to follow Android best
396 * practices for intent filtering, for example to force lower-case.
397 * However the unchecked exception
398 * {@link IllegalArgumentException} may be thrown
399 * if the mimeType parameter has serious problems,
400 * for example if it is empty, so always catch this
401 * exception if you are passing user-generated data into this method.
402 * <p>
Nick Pellya356bf12011-12-13 15:36:31 -0800403 * For efficiency, This method might not make an internal copy of the
404 * mimeData byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100405 * to modify the mimeData byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800406 * NdefRecord.
407 *
Nick Pellyc97a5522012-01-05 15:13:01 +1100408 * @param mimeType a valid MIME type
Nick Pellya356bf12011-12-13 15:36:31 -0800409 * @param mimeData MIME data as bytes
410 * @return an NDEF Record containing the MIME-typed data
Nick Pellyc97a5522012-01-05 15:13:01 +1100411 * @throws IllegalArugmentException if the mimeType is empty or invalid
412 *
Nick Pellya356bf12011-12-13 15:36:31 -0800413 */
414 public static NdefRecord createMime(String mimeType, byte[] mimeData) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100415 if (mimeType == null) throw new NullPointerException("mimeType is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800416
Nick Pellyc97a5522012-01-05 15:13:01 +1100417 // We only do basic MIME type validation: trying to follow the
418 // RFCs strictly only ends in tears, since there are lots of MIME
419 // types in common use that are not strictly valid as per RFC rules
420 mimeType = Intent.normalizeMimeType(mimeType);
421 if (mimeType.length() == 0) throw new IllegalArgumentException("mimeType is empty");
422 int slashIndex = mimeType.indexOf('/');
423 if (slashIndex == 0) throw new IllegalArgumentException("mimeType must have major type");
424 if (slashIndex == mimeType.length() - 1) {
425 throw new IllegalArgumentException("mimeType must have minor type");
426 }
427 // missing '/' is allowed
428
429 // MIME RFCs suggest ASCII encoding for content-type
Elliott Hughesd396a442013-06-28 16:24:48 -0700430 byte[] typeBytes = mimeType.getBytes(StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100431 return new NdefRecord(TNF_MIME_MEDIA, typeBytes, null, mimeData);
Nick Pellya356bf12011-12-13 15:36:31 -0800432 }
433
434 /**
435 * Create a new NDEF Record containing external (application-specific) data.<p>
436 * Use this method to encode application specific data into an NDEF Record.
437 * The data is typed by a domain name (usually your Android package name) and
438 * a domain-specific type. This data is packaged into a "NFC Forum External
439 * Type" NDEF Record.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100440 * NFC Forum requires that the domain and type used in an external record
441 * are treated as case insensitive, however Android intent filtering is
442 * always case sensitive. So this method will force the domain and type to
443 * lower-case before creating the NDEF Record.<p>
444 * The unchecked exception {@link IllegalArgumentException} will be thrown
445 * if the domain and type have serious problems, for example if either field
446 * is empty, so always catch this
447 * exception if you are passing user-generated data into this method.<p>
448 * There are no such restrictions on the payload data.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800449 * For efficiency, This method might not make an internal copy of the
450 * data byte array, so take care not
Nick Pellyc97a5522012-01-05 15:13:01 +1100451 * to modify the data byte array while still using the returned
Nick Pellya356bf12011-12-13 15:36:31 -0800452 * NdefRecord.
453 *
454 * Reference specification: NFCForum-TS-RTD_1.0
455 * @param domain domain-name of issuing organization
456 * @param type domain-specific type of data
457 * @param data payload as bytes
Nick Pellyc97a5522012-01-05 15:13:01 +1100458 * @throws IllegalArugmentException if either domain or type are empty or invalid
Nick Pellya356bf12011-12-13 15:36:31 -0800459 */
460 public static NdefRecord createExternal(String domain, String type, byte[] data) {
Nick Pellyc97a5522012-01-05 15:13:01 +1100461 if (domain == null) throw new NullPointerException("domain is null");
462 if (type == null) throw new NullPointerException("type is null");
Nick Pellya356bf12011-12-13 15:36:31 -0800463
Elliott Hughescb64d432013-08-02 10:00:44 -0700464 domain = domain.trim().toLowerCase(Locale.ROOT);
465 type = type.trim().toLowerCase(Locale.ROOT);
Nick Pellyc97a5522012-01-05 15:13:01 +1100466
467 if (domain.length() == 0) throw new IllegalArgumentException("domain is empty");
468 if (type.length() == 0) throw new IllegalArgumentException("type is empty");
469
Elliott Hughesd396a442013-06-28 16:24:48 -0700470 byte[] byteDomain = domain.getBytes(StandardCharsets.UTF_8);
471 byte[] byteType = type.getBytes(StandardCharsets.UTF_8);
Nick Pellya356bf12011-12-13 15:36:31 -0800472 byte[] b = new byte[byteDomain.length + 1 + byteType.length];
473 System.arraycopy(byteDomain, 0, b, 0, byteDomain.length);
474 b[byteDomain.length] = ':';
475 System.arraycopy(byteType, 0, b, byteDomain.length + 1, byteType.length);
476
477 return new NdefRecord(TNF_EXTERNAL_TYPE, b, null, data);
478 }
479
480 /**
Martijn Coenen7fe9fa12014-01-29 17:28:04 -0800481 * Create a new NDEF record containing UTF-8 text data.<p>
482 *
483 * The caller can either specify the language code for the provided text,
484 * or otherwise the language code corresponding to the current default
485 * locale will be used.
486 *
487 * Reference specification: NFCForum-TS-RTD_Text_1.0
488 * @param languageCode The languageCode for the record. If locale is empty or null,
489 * the language code of the current default locale will be used.
490 * @param text The text to be encoded in the record. Will be represented in UTF-8 format.
491 * @throws IllegalArgumentException if text is null
492 */
493 public static NdefRecord createTextRecord(String languageCode, String text) {
494 if (text == null) throw new NullPointerException("text is null");
495
496 byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
497
498 byte[] languageCodeBytes = null;
499 if (languageCode != null && !languageCode.isEmpty()) {
500 languageCodeBytes = languageCode.getBytes(StandardCharsets.US_ASCII);
501 } else {
502 languageCodeBytes = Locale.getDefault().getLanguage().
503 getBytes(StandardCharsets.US_ASCII);
504 }
505 // We only have 6 bits to indicate ISO/IANA language code.
506 if (languageCodeBytes.length >= 64) {
507 throw new IllegalArgumentException("language code is too long, must be <64 bytes.");
508 }
509 ByteBuffer buffer = ByteBuffer.allocate(1 + languageCodeBytes.length + textBytes.length);
510
511 byte status = (byte) (languageCodeBytes.length & 0xFF);
512 buffer.put(status);
513 buffer.put(languageCodeBytes);
514 buffer.put(textBytes);
515
516 return new NdefRecord(TNF_WELL_KNOWN, RTD_TEXT, null, buffer.array());
517 }
518
519 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800520 * Construct an NDEF Record from its component fields.<p>
521 * Recommend to use helpers such as {#createUri} or
522 * {{@link #createExternal} where possible, since they perform
523 * stricter validation that the record is correctly formatted
524 * as per NDEF specifications. However if you know what you are
525 * doing then this constructor offers the most flexibility.<p>
526 * An {@link NdefRecord} represents a logical (complete)
527 * record, and cannot represent NDEF Record chunks.<p>
528 * Basic validation of the tnf, type, id and payload is performed
529 * as per the following rules:
530 * <ul>
531 * <li>The tnf paramter must be a 3-bit value.</li>
532 * <li>Records with a tnf of {@link #TNF_EMPTY} cannot have a type,
533 * id or payload.</li>
534 * <li>Records with a tnf of {@link #TNF_UNKNOWN} or {@literal 0x07}
535 * cannot have a type.</li>
536 * <li>Records with a tnf of {@link #TNF_UNCHANGED} are not allowed
537 * since this class only represents complete (unchunked) records.</li>
538 * </ul>
539 * This minimal validation is specified by
540 * NFCForum-TS-NDEF_1.0 section 3.2.6 (Type Name Format).<p>
541 * If any of the above validation
542 * steps fail then {@link IllegalArgumentException} is thrown.<p>
543 * Deep inspection of the type, id and payload fields is not
544 * performed, so it is possible to create NDEF Records
545 * that conform to section 3.2.6
546 * but fail other more strict NDEF specification requirements. For
547 * example, the payload may be invalid given the tnf and type.
548 * <p>
549 * To omit a type, id or payload field, set the parameter to an
550 * empty byte array or null.
Nick Pellydc993792010-10-04 11:17:25 -0700551 *
552 * @param tnf a 3-bit TNF constant
Nick Pellya356bf12011-12-13 15:36:31 -0800553 * @param type byte array, containing zero to 255 bytes, or null
554 * @param id byte array, containing zero to 255 bytes, or null
Nick Pellydc993792010-10-04 11:17:25 -0700555 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes,
Nick Pellya356bf12011-12-13 15:36:31 -0800556 * or null
557 * @throws IllegalArugmentException if a valid record cannot be created
Nick Pellydc993792010-10-04 11:17:25 -0700558 */
559 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) {
Nick Pellya356bf12011-12-13 15:36:31 -0800560 /* convert nulls */
561 if (type == null) type = EMPTY_BYTE_ARRAY;
562 if (id == null) id = EMPTY_BYTE_ARRAY;
563 if (payload == null) payload = EMPTY_BYTE_ARRAY;
Martijn Coenen8bede172011-04-08 16:42:22 +0200564
Nick Pellya356bf12011-12-13 15:36:31 -0800565 String message = validateTnf(tnf, type, id, payload);
566 if (message != null) {
567 throw new IllegalArgumentException(message);
Nick Pelly590b73b2010-10-12 13:00:50 -0700568 }
569
Nick Pelly590b73b2010-10-12 13:00:50 -0700570 mTnf = tnf;
Nick Pellya356bf12011-12-13 15:36:31 -0800571 mType = type;
572 mId = id;
573 mPayload = payload;
Nick Pellydc993792010-10-04 11:17:25 -0700574 }
575
576 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800577 * Construct an NDEF Record from raw bytes.<p>
578 * This method is deprecated, use {@link NdefMessage#NdefMessage(byte[])}
579 * instead. This is because it does not make sense to parse a record:
580 * the NDEF binary format is only defined for a message, and the
581 * record flags MB and ME do not make sense outside of the context of
582 * an entire message.<p>
583 * This implementation will attempt to parse a single record by ignoring
584 * the MB and ME flags, and otherwise following the rules of
585 * {@link NdefMessage#NdefMessage(byte[])}.<p>
Nick Pellydc993792010-10-04 11:17:25 -0700586 *
Nick Pellya356bf12011-12-13 15:36:31 -0800587 * @param data raw bytes to parse
588 * @throws FormatException if the data cannot be parsed into a valid record
589 * @deprecated use {@link NdefMessage#NdefMessage(byte[])} instead.
Nick Pellydc993792010-10-04 11:17:25 -0700590 */
Nick Pellya356bf12011-12-13 15:36:31 -0800591 @Deprecated
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700592 public NdefRecord(byte[] data) throws FormatException {
Nick Pellya356bf12011-12-13 15:36:31 -0800593 ByteBuffer buffer = ByteBuffer.wrap(data);
594 NdefRecord[] rs = parse(buffer, true);
595
596 if (buffer.remaining() > 0) {
597 throw new FormatException("data too long");
Sylvain Fonteneaudd7341f2010-10-17 15:32:33 -0700598 }
Nick Pellya356bf12011-12-13 15:36:31 -0800599
600 mTnf = rs[0].mTnf;
601 mType = rs[0].mType;
602 mId = rs[0].mId;
603 mPayload = rs[0].mPayload;
Nick Pellydc993792010-10-04 11:17:25 -0700604 }
605
606 /**
607 * Returns the 3-bit TNF.
608 * <p>
609 * TNF is the top-level type.
610 */
611 public short getTnf() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700612 return mTnf;
Nick Pellydc993792010-10-04 11:17:25 -0700613 }
614
615 /**
616 * Returns the variable length Type field.
617 * <p>
618 * This should be used in conjunction with the TNF field to determine the
619 * payload format.
Nick Pellya356bf12011-12-13 15:36:31 -0800620 * <p>
621 * Returns an empty byte array if this record
622 * does not have a type field.
Nick Pellydc993792010-10-04 11:17:25 -0700623 */
624 public byte[] getType() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700625 return mType.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700626 }
627
628 /**
629 * Returns the variable length ID.
Nick Pellya356bf12011-12-13 15:36:31 -0800630 * <p>
631 * Returns an empty byte array if this record
632 * does not have an id field.
Nick Pellydc993792010-10-04 11:17:25 -0700633 */
634 public byte[] getId() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700635 return mId.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700636 }
637
638 /**
639 * Returns the variable length payload.
Nick Pellya356bf12011-12-13 15:36:31 -0800640 * <p>
641 * Returns an empty byte array if this record
642 * does not have a payload field.
Nick Pellydc993792010-10-04 11:17:25 -0700643 */
644 public byte[] getPayload() {
Nick Pelly590b73b2010-10-12 13:00:50 -0700645 return mPayload.clone();
Nick Pellydc993792010-10-04 11:17:25 -0700646 }
647
648 /**
Nick Pellya356bf12011-12-13 15:36:31 -0800649 * Return this NDEF Record as a byte array.<p>
650 * This method is deprecated, use {@link NdefMessage#toByteArray}
651 * instead. This is because the NDEF binary format is not defined for
652 * a record outside of the context of a message: the MB and ME flags
653 * cannot be set without knowing the location inside a message.<p>
654 * This implementation will attempt to serialize a single record by
655 * always setting the MB and ME flags (in other words, assume this
656 * is a single-record NDEF Message).<p>
657 *
658 * @deprecated use {@link NdefMessage#toByteArray()} instead
659 */
660 @Deprecated
661 public byte[] toByteArray() {
662 ByteBuffer buffer = ByteBuffer.allocate(getByteLength());
663 writeToByteBuffer(buffer, true, true);
664 return buffer.array();
665 }
666
667 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100668 * Map this record to a MIME type, or return null if it cannot be mapped.<p>
669 * Currently this method considers all {@link #TNF_MIME_MEDIA} records to
670 * be MIME records, as well as some {@link #TNF_WELL_KNOWN} records such as
671 * {@link #RTD_TEXT}. If this is a MIME record then the MIME type as string
672 * is returned, otherwise null is returned.<p>
673 * This method does not perform validation that the MIME type is
674 * actually valid. It always attempts to
675 * return a string containing the type if this is a MIME record.<p>
676 * The returned MIME type will by normalized to lower-case using
677 * {@link Intent#normalizeMimeType}.<p>
678 * The MIME payload can be obtained using {@link #getPayload}.
679 *
680 * @return MIME type as a string, or null if this is not a MIME record
Nick Pellye0180d02011-06-07 17:27:44 -0700681 */
Nick Pellyc97a5522012-01-05 15:13:01 +1100682 public String toMimeType() {
683 switch (mTnf) {
684 case NdefRecord.TNF_WELL_KNOWN:
685 if (Arrays.equals(mType, NdefRecord.RTD_TEXT)) {
686 return "text/plain";
687 }
688 break;
689 case NdefRecord.TNF_MIME_MEDIA:
Elliott Hughesd396a442013-06-28 16:24:48 -0700690 String mimeType = new String(mType, StandardCharsets.US_ASCII);
Nick Pellyc97a5522012-01-05 15:13:01 +1100691 return Intent.normalizeMimeType(mimeType);
Nick Pellye0180d02011-06-07 17:27:44 -0700692 }
Nick Pellyc97a5522012-01-05 15:13:01 +1100693 return null;
Nick Pellye0180d02011-06-07 17:27:44 -0700694 }
695
696 /**
Nick Pellyc97a5522012-01-05 15:13:01 +1100697 * Map this record to a URI, or return null if it cannot be mapped.<p>
698 * Currently this method considers the following to be URI records:
699 * <ul>
700 * <li>{@link #TNF_ABSOLUTE_URI} records.</li>
701 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_URI}.</li>
702 * <li>{@link #TNF_WELL_KNOWN} with a type of {@link #RTD_SMART_POSTER}
703 * and containing a URI record in the NDEF message nested in the payload.
704 * </li>
705 * <li>{@link #TNF_EXTERNAL_TYPE} records.</li>
706 * </ul>
707 * If this is not a URI record by the above rules, then null is returned.<p>
708 * This method does not perform validation that the URI is
709 * actually valid: it always attempts to create and return a URI if
710 * this record appears to be a URI record by the above rules.<p>
711 * The returned URI will be normalized to have a lower case scheme
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400712 * using {@link Uri#normalizeScheme}.<p>
Nick Pellyc97a5522012-01-05 15:13:01 +1100713 *
714 * @return URI, or null if this is not a URI record
715 */
716 public Uri toUri() {
717 return toUri(false);
718 }
719
720 private Uri toUri(boolean inSmartPoster) {
721 switch (mTnf) {
722 case TNF_WELL_KNOWN:
723 if (Arrays.equals(mType, RTD_SMART_POSTER) && !inSmartPoster) {
724 try {
725 // check payload for a nested NDEF Message containing a URI
726 NdefMessage nestedMessage = new NdefMessage(mPayload);
727 for (NdefRecord nestedRecord : nestedMessage.getRecords()) {
728 Uri uri = nestedRecord.toUri(true);
729 if (uri != null) {
730 return uri;
731 }
732 }
733 } catch (FormatException e) { }
734 } else if (Arrays.equals(mType, RTD_URI)) {
Martijn Coenen32ac1e12012-09-06 17:09:06 +0200735 Uri wktUri = parseWktUri();
736 return (wktUri != null ? wktUri.normalizeScheme() : null);
Nick Pellyc97a5522012-01-05 15:13:01 +1100737 }
738 break;
739
740 case TNF_ABSOLUTE_URI:
Elliott Hughesd396a442013-06-28 16:24:48 -0700741 Uri uri = Uri.parse(new String(mType, StandardCharsets.UTF_8));
Jesse Wilsonabc43dd2012-05-10 14:29:33 -0400742 return uri.normalizeScheme();
Nick Pellyc97a5522012-01-05 15:13:01 +1100743
744 case TNF_EXTERNAL_TYPE:
745 if (inSmartPoster) {
746 break;
747 }
Elliott Hughesd396a442013-06-28 16:24:48 -0700748 return Uri.parse("vnd.android.nfc://ext/" + new String(mType, StandardCharsets.US_ASCII));
Nick Pellyc97a5522012-01-05 15:13:01 +1100749 }
750 return null;
751 }
752
753 /**
754 * Return complete URI of {@link #TNF_WELL_KNOWN}, {@link #RTD_URI} records.
755 * @return complete URI, or null if invalid
756 */
757 private Uri parseWktUri() {
758 if (mPayload.length < 2) {
759 return null;
760 }
761
762 // payload[0] contains the URI Identifier Code, as per
763 // NFC Forum "URI Record Type Definition" section 3.2.2.
764 int prefixIndex = (mPayload[0] & (byte)0xFF);
765 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) {
766 return null;
767 }
768 String prefix = URI_PREFIX_MAP[prefixIndex];
769 String suffix = new String(Arrays.copyOfRange(mPayload, 1, mPayload.length),
Elliott Hughesd396a442013-06-28 16:24:48 -0700770 StandardCharsets.UTF_8);
Nick Pellyc97a5522012-01-05 15:13:01 +1100771 return Uri.parse(prefix + suffix);
772 }
773
774 /**
775 * Main record parsing method.<p>
Nick Pellya356bf12011-12-13 15:36:31 -0800776 * Expects NdefMessage to begin immediately, allows trailing data.<p>
777 * Currently has strict validation of all fields as per NDEF 1.0
778 * specification section 2.5. We will attempt to keep this as strict as
779 * possible to encourage well-formatted NDEF.<p>
780 * Always returns 1 or more NdefRecord's, or throws FormatException.
781 *
782 * @param buffer ByteBuffer to read from
783 * @param ignoreMbMe ignore MB and ME flags, and read only 1 complete record
784 * @return one or more records
785 * @throws FormatException on any parsing error
Nick Pellydc993792010-10-04 11:17:25 -0700786 */
Nick Pellya356bf12011-12-13 15:36:31 -0800787 static NdefRecord[] parse(ByteBuffer buffer, boolean ignoreMbMe) throws FormatException {
788 List<NdefRecord> records = new ArrayList<NdefRecord>();
789
790 try {
791 byte[] type = null;
792 byte[] id = null;
793 byte[] payload = null;
794 ArrayList<byte[]> chunks = new ArrayList<byte[]>();
795 boolean inChunk = false;
796 short chunkTnf = -1;
797 boolean me = false;
798
799 while (!me) {
800 byte flag = buffer.get();
801
802 boolean mb = (flag & NdefRecord.FLAG_MB) != 0;
803 me = (flag & NdefRecord.FLAG_ME) != 0;
804 boolean cf = (flag & NdefRecord.FLAG_CF) != 0;
805 boolean sr = (flag & NdefRecord.FLAG_SR) != 0;
806 boolean il = (flag & NdefRecord.FLAG_IL) != 0;
807 short tnf = (short)(flag & 0x07);
808
809 if (!mb && records.size() == 0 && !inChunk && !ignoreMbMe) {
810 throw new FormatException("expected MB flag");
Ruchi Kandoi2ed0b482017-01-24 18:32:01 -0800811 } else if (mb && (records.size() != 0 || inChunk) && !ignoreMbMe) {
Nick Pellya356bf12011-12-13 15:36:31 -0800812 throw new FormatException("unexpected MB flag");
813 } else if (inChunk && il) {
814 throw new FormatException("unexpected IL flag in non-leading chunk");
815 } else if (cf && me) {
816 throw new FormatException("unexpected ME flag in non-trailing chunk");
817 } else if (inChunk && tnf != NdefRecord.TNF_UNCHANGED) {
818 throw new FormatException("expected TNF_UNCHANGED in non-leading chunk");
819 } else if (!inChunk && tnf == NdefRecord.TNF_UNCHANGED) {
820 throw new FormatException("" +
John Spurlock8a985d22014-02-25 09:40:05 -0500821 "unexpected TNF_UNCHANGED in first chunk or unchunked record");
Nick Pellya356bf12011-12-13 15:36:31 -0800822 }
823
824 int typeLength = buffer.get() & 0xFF;
825 long payloadLength = sr ? (buffer.get() & 0xFF) : (buffer.getInt() & 0xFFFFFFFFL);
826 int idLength = il ? (buffer.get() & 0xFF) : 0;
827
828 if (inChunk && typeLength != 0) {
829 throw new FormatException("expected zero-length type in non-leading chunk");
830 }
831
832 if (!inChunk) {
833 type = (typeLength > 0 ? new byte[typeLength] : EMPTY_BYTE_ARRAY);
834 id = (idLength > 0 ? new byte[idLength] : EMPTY_BYTE_ARRAY);
835 buffer.get(type);
836 buffer.get(id);
837 }
838
839 ensureSanePayloadSize(payloadLength);
840 payload = (payloadLength > 0 ? new byte[(int)payloadLength] : EMPTY_BYTE_ARRAY);
841 buffer.get(payload);
842
843 if (cf && !inChunk) {
844 // first chunk
Ruchi Kandoi2b527ac2017-02-03 14:01:09 -0800845 if (typeLength == 0 && tnf != NdefRecord.TNF_UNKNOWN) {
Ruchi Kandoi2ed0b482017-01-24 18:32:01 -0800846 throw new FormatException("expected non-zero type length in first chunk");
847 }
Nick Pellya356bf12011-12-13 15:36:31 -0800848 chunks.clear();
849 chunkTnf = tnf;
850 }
851 if (cf || inChunk) {
852 // any chunk
853 chunks.add(payload);
854 }
855 if (!cf && inChunk) {
856 // last chunk, flatten the payload
857 payloadLength = 0;
858 for (byte[] p : chunks) {
859 payloadLength += p.length;
860 }
861 ensureSanePayloadSize(payloadLength);
862 payload = new byte[(int)payloadLength];
863 int i = 0;
864 for (byte[] p : chunks) {
865 System.arraycopy(p, 0, payload, i, p.length);
866 i += p.length;
867 }
868 tnf = chunkTnf;
869 }
870 if (cf) {
871 // more chunks to come
872 inChunk = true;
873 continue;
874 } else {
875 inChunk = false;
876 }
877
878 String error = validateTnf(tnf, type, id, payload);
879 if (error != null) {
880 throw new FormatException(error);
881 }
882 records.add(new NdefRecord(tnf, type, id, payload));
883 if (ignoreMbMe) { // for parsing a single NdefRecord
884 break;
885 }
886 }
887 } catch (BufferUnderflowException e) {
888 throw new FormatException("expected more data", e);
889 }
890 return records.toArray(new NdefRecord[records.size()]);
Nick Pellydc993792010-10-04 11:17:25 -0700891 }
892
Nick Pellya356bf12011-12-13 15:36:31 -0800893 private static void ensureSanePayloadSize(long size) throws FormatException {
894 if (size > MAX_PAYLOAD_SIZE) {
895 throw new FormatException(
896 "payload above max limit: " + size + " > " + MAX_PAYLOAD_SIZE);
897 }
898 }
899
900 /**
901 * Perform simple validation that the tnf is valid.<p>
902 * Validates the requirements of NFCForum-TS-NDEF_1.0 section
903 * 3.2.6 (Type Name Format). This just validates that the tnf
904 * is valid, and that the relevant type, id and payload
905 * fields are present (or empty) for this tnf. It does not
906 * perform any deep inspection of the type, id and payload fields.<p>
907 * Also does not allow TNF_UNCHANGED since this class is only used
908 * to present logical (unchunked) records.
909 *
910 * @return null if valid, or a string error if invalid.
911 */
912 static String validateTnf(short tnf, byte[] type, byte[] id, byte[] payload) {
913 switch (tnf) {
914 case TNF_EMPTY:
915 if (type.length != 0 || id.length != 0 || payload.length != 0) {
916 return "unexpected data in TNF_EMPTY record";
917 }
918 return null;
919 case TNF_WELL_KNOWN:
920 case TNF_MIME_MEDIA:
921 case TNF_ABSOLUTE_URI:
922 case TNF_EXTERNAL_TYPE:
923 return null;
924 case TNF_UNKNOWN:
925 case TNF_RESERVED:
926 if (type.length != 0) {
927 return "unexpected type field in TNF_UNKNOWN or TNF_RESERVEd record";
928 }
929 return null;
930 case TNF_UNCHANGED:
931 return "unexpected TNF_UNCHANGED in first chunk or logical record";
932 default:
933 return String.format("unexpected tnf value: 0x%02x", tnf);
934 }
935 }
936
937 /**
938 * Serialize record for network transmission.<p>
939 * Uses specified MB and ME flags.<p>
940 * Does not chunk records.
941 */
942 void writeToByteBuffer(ByteBuffer buffer, boolean mb, boolean me) {
943 boolean sr = mPayload.length < 256;
Yuka Anamidd0c9bb2017-05-15 13:53:04 +0900944 boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
Nick Pellya356bf12011-12-13 15:36:31 -0800945
946 byte flags = (byte)((mb ? FLAG_MB : 0) | (me ? FLAG_ME : 0) |
947 (sr ? FLAG_SR : 0) | (il ? FLAG_IL : 0) | mTnf);
948 buffer.put(flags);
949
950 buffer.put((byte)mType.length);
951 if (sr) {
952 buffer.put((byte)mPayload.length);
953 } else {
954 buffer.putInt(mPayload.length);
955 }
956 if (il) {
957 buffer.put((byte)mId.length);
958 }
959
960 buffer.put(mType);
961 buffer.put(mId);
962 buffer.put(mPayload);
963 }
964
965 /**
966 * Get byte length of serialized record.
967 */
968 int getByteLength() {
969 int length = 3 + mType.length + mId.length + mPayload.length;
970
971 boolean sr = mPayload.length < 256;
Yuka Anamidd0c9bb2017-05-15 13:53:04 +0900972 boolean il = mTnf == TNF_EMPTY ? true : mId.length > 0;
Nick Pellya356bf12011-12-13 15:36:31 -0800973
974 if (!sr) length += 3;
975 if (il) length += 1;
976
977 return length;
978 }
979
980 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700981 public int describeContents() {
982 return 0;
983 }
984
Nick Pellya356bf12011-12-13 15:36:31 -0800985 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700986 public void writeToParcel(Parcel dest, int flags) {
Nick Pelly590b73b2010-10-12 13:00:50 -0700987 dest.writeInt(mTnf);
988 dest.writeInt(mType.length);
989 dest.writeByteArray(mType);
990 dest.writeInt(mId.length);
991 dest.writeByteArray(mId);
992 dest.writeInt(mPayload.length);
993 dest.writeByteArray(mPayload);
Nick Pellydc993792010-10-04 11:17:25 -0700994 }
995
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700996 public static final @android.annotation.NonNull Parcelable.Creator<NdefRecord> CREATOR =
Nick Pellydc993792010-10-04 11:17:25 -0700997 new Parcelable.Creator<NdefRecord>() {
Nick Pellya356bf12011-12-13 15:36:31 -0800998 @Override
Nick Pellydc993792010-10-04 11:17:25 -0700999 public NdefRecord createFromParcel(Parcel in) {
Nick Pelly590b73b2010-10-12 13:00:50 -07001000 short tnf = (short)in.readInt();
1001 int typeLength = in.readInt();
1002 byte[] type = new byte[typeLength];
1003 in.readByteArray(type);
1004 int idLength = in.readInt();
1005 byte[] id = new byte[idLength];
1006 in.readByteArray(id);
1007 int payloadLength = in.readInt();
1008 byte[] payload = new byte[payloadLength];
1009 in.readByteArray(payload);
1010
Nick Pellya356bf12011-12-13 15:36:31 -08001011 return new NdefRecord(tnf, type, id, payload);
Nick Pellydc993792010-10-04 11:17:25 -07001012 }
Nick Pellya356bf12011-12-13 15:36:31 -08001013 @Override
Nick Pellydc993792010-10-04 11:17:25 -07001014 public NdefRecord[] newArray(int size) {
Nick Pelly590b73b2010-10-12 13:00:50 -07001015 return new NdefRecord[size];
Nick Pellydc993792010-10-04 11:17:25 -07001016 }
1017 };
Nick Pelly590b73b2010-10-12 13:00:50 -07001018
Nick Pellya356bf12011-12-13 15:36:31 -08001019 @Override
1020 public int hashCode() {
1021 final int prime = 31;
1022 int result = 1;
1023 result = prime * result + Arrays.hashCode(mId);
1024 result = prime * result + Arrays.hashCode(mPayload);
1025 result = prime * result + mTnf;
1026 result = prime * result + Arrays.hashCode(mType);
1027 return result;
1028 }
1029
1030 /**
1031 * Returns true if the specified NDEF Record contains
1032 * identical tnf, type, id and payload fields.
1033 */
1034 @Override
1035 public boolean equals(Object obj) {
1036 if (this == obj) return true;
1037 if (obj == null) return false;
1038 if (getClass() != obj.getClass()) return false;
1039 NdefRecord other = (NdefRecord) obj;
1040 if (!Arrays.equals(mId, other.mId)) return false;
1041 if (!Arrays.equals(mPayload, other.mPayload)) return false;
1042 if (mTnf != other.mTnf) return false;
1043 return Arrays.equals(mType, other.mType);
1044 }
1045
1046 @Override
1047 public String toString() {
1048 StringBuilder b = new StringBuilder(String.format("NdefRecord tnf=%X", mTnf));
1049 if (mType.length > 0) b.append(" type=").append(bytesToString(mType));
1050 if (mId.length > 0) b.append(" id=").append(bytesToString(mId));
1051 if (mPayload.length > 0) b.append(" payload=").append(bytesToString(mPayload));
1052 return b.toString();
1053 }
1054
Mike Ma5313cbc2020-01-22 13:28:16 -08001055 /**
1056 * Dump debugging information as a NdefRecordProto
1057 * @hide
1058 *
1059 * Note:
1060 * See proto definition in frameworks/base/core/proto/android/nfc/ndef.proto
1061 * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
1062 * {@link ProtoOutputStream#end(long)} after.
1063 * Never reuse a proto field number. When removing a field, mark it as reserved.
1064 */
1065 public void dumpDebug(ProtoOutputStream proto) {
1066 proto.write(NdefRecordProto.TYPE, mType);
1067 proto.write(NdefRecordProto.ID, mId);
1068 proto.write(NdefRecordProto.PAYLOAD_BYTES, mPayload.length);
1069 }
1070
Nick Pellya356bf12011-12-13 15:36:31 -08001071 private static StringBuilder bytesToString(byte[] bs) {
1072 StringBuilder s = new StringBuilder();
1073 for (byte b : bs) {
1074 s.append(String.format("%02X", b));
1075 }
1076 return s;
1077 }
Martijn Coenen8bede172011-04-08 16:42:22 +02001078}