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