NdefRecord.java revision a37fcbce59c8a746f641936b4de99867dbfabac9
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 // TODO unhide for ICS 156 // TODO recheck docs 157 public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes(); 158 159 private static final byte FLAG_MB = (byte) 0x80; 160 private static final byte FLAG_ME = (byte) 0x40; 161 private static final byte FLAG_CF = (byte) 0x20; 162 private static final byte FLAG_SR = (byte) 0x10; 163 private static final byte FLAG_IL = (byte) 0x08; 164 165 /** 166 * NFC Forum "URI Record Type Definition" 167 * 168 * This is a mapping of "URI Identifier Codes" to URI string prefixes, 169 * per section 3.2.2 of the NFC Forum URI Record Type Definition document. 170 */ 171 private static final String[] URI_PREFIX_MAP = new String[] { 172 "", // 0x00 173 "http://www.", // 0x01 174 "https://www.", // 0x02 175 "http://", // 0x03 176 "https://", // 0x04 177 "tel:", // 0x05 178 "mailto:", // 0x06 179 "ftp://anonymous:anonymous@", // 0x07 180 "ftp://ftp.", // 0x08 181 "ftps://", // 0x09 182 "sftp://", // 0x0A 183 "smb://", // 0x0B 184 "nfs://", // 0x0C 185 "ftp://", // 0x0D 186 "dav://", // 0x0E 187 "news:", // 0x0F 188 "telnet://", // 0x10 189 "imap:", // 0x11 190 "rtsp://", // 0x12 191 "urn:", // 0x13 192 "pop:", // 0x14 193 "sip:", // 0x15 194 "sips:", // 0x16 195 "tftp:", // 0x17 196 "btspp://", // 0x18 197 "btl2cap://", // 0x19 198 "btgoep://", // 0x1A 199 "tcpobex://", // 0x1B 200 "irdaobex://", // 0x1C 201 "file://", // 0x1D 202 "urn:epc:id:", // 0x1E 203 "urn:epc:tag:", // 0x1F 204 "urn:epc:pat:", // 0x20 205 "urn:epc:raw:", // 0x21 206 "urn:epc:", // 0x22 207 }; 208 209 private final byte mFlags; 210 private final short mTnf; 211 private final byte[] mType; 212 private final byte[] mId; 213 private final byte[] mPayload; 214 215 /** 216 * Construct an NDEF Record. 217 * <p> 218 * Applications should not attempt to manually chunk NDEF Records - the 219 * implementation of android.nfc will automatically chunk an NDEF Record 220 * when necessary (and only present a single logical NDEF Record to the 221 * application). So applications should not use TNF_UNCHANGED. 222 * 223 * @param tnf a 3-bit TNF constant 224 * @param type byte array, containing zero to 255 bytes, must not be null 225 * @param id byte array, containing zero to 255 bytes, must not be null 226 * @param payload byte array, containing zero to (2 ** 32 - 1) bytes, 227 * must not be null 228 */ 229 public NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload) { 230 /* New NDEF records created by applications will have FLAG_MB|FLAG_ME 231 * set by default; when multiple records are stored in a 232 * {@link NdefMessage}, these flags will be corrected when the {@link NdefMessage} 233 * is serialized to bytes. 234 */ 235 this(tnf, type, id, payload, (byte)(FLAG_MB|FLAG_ME)); 236 } 237 238 /** 239 * @hide 240 */ 241 /*package*/ NdefRecord(short tnf, byte[] type, byte[] id, byte[] payload, byte flags) { 242 /* check arguments */ 243 if ((type == null) || (id == null) || (payload == null)) { 244 throw new IllegalArgumentException("Illegal null argument"); 245 } 246 247 if (tnf < 0 || tnf > 0x07) { 248 throw new IllegalArgumentException("TNF out of range " + tnf); 249 } 250 251 /* Determine if it is a short record */ 252 if(payload.length < 0xFF) { 253 flags |= FLAG_SR; 254 } 255 256 /* Determine if an id is present */ 257 if(id.length != 0) { 258 flags |= FLAG_IL; 259 } 260 261 mFlags = flags; 262 mTnf = tnf; 263 mType = type.clone(); 264 mId = id.clone(); 265 mPayload = payload.clone(); 266 } 267 268 /** 269 * Construct an NDEF Record from raw bytes. 270 * <p> 271 * Validation is performed to make sure the header is valid, and that 272 * the id, type and payload sizes appear to be valid. 273 * 274 * @throws FormatException if the data is not a valid NDEF record 275 */ 276 public NdefRecord(byte[] data) throws FormatException { 277 /* Prevent compiler to complain about unassigned final fields */ 278 mFlags = 0; 279 mTnf = 0; 280 mType = null; 281 mId = null; 282 mPayload = null; 283 /* Perform actual parsing */ 284 if (parseNdefRecord(data) == -1) { 285 throw new FormatException("Error while parsing NDEF record"); 286 } 287 } 288 289 /** 290 * Returns the 3-bit TNF. 291 * <p> 292 * TNF is the top-level type. 293 */ 294 public short getTnf() { 295 return mTnf; 296 } 297 298 /** 299 * Returns the variable length Type field. 300 * <p> 301 * This should be used in conjunction with the TNF field to determine the 302 * payload format. 303 */ 304 public byte[] getType() { 305 return mType.clone(); 306 } 307 308 /** 309 * Returns the variable length ID. 310 */ 311 public byte[] getId() { 312 return mId.clone(); 313 } 314 315 /** 316 * Returns the variable length payload. 317 */ 318 public byte[] getPayload() { 319 return mPayload.clone(); 320 } 321 322 /** 323 * Helper to return the NdefRecord as a URI. 324 * TODO: Consider making a member method instead of static 325 * TODO: Consider more validation that this is a URI record 326 * TODO: Make a public API 327 * @hide 328 */ 329 public static Uri parseWellKnownUriRecord(NdefRecord record) throws FormatException { 330 byte[] payload = record.getPayload(); 331 if (payload.length < 2) { 332 throw new FormatException("Payload is not a valid URI (missing prefix)"); 333 } 334 335 /* 336 * payload[0] contains the URI Identifier Code, per the 337 * NFC Forum "URI Record Type Definition" section 3.2.2. 338 * 339 * payload[1]...payload[payload.length - 1] contains the rest of 340 * the URI. 341 */ 342 int prefixIndex = (payload[0] & 0xff); 343 if (prefixIndex < 0 || prefixIndex >= URI_PREFIX_MAP.length) { 344 throw new FormatException("Payload is not a valid URI (invalid prefix)"); 345 } 346 String prefix = URI_PREFIX_MAP[prefixIndex]; 347 byte[] fullUri = concat(prefix.getBytes(Charsets.UTF_8), 348 Arrays.copyOfRange(payload, 1, payload.length)); 349 return Uri.parse(new String(fullUri, Charsets.UTF_8)); 350 } 351 352 /** 353 * Creates an Android application NDEF record. 354 * <p> 355 * When an Android device dispatches an {@link NdefMessage} 356 * containing one or more Android application records, 357 * the applications contained in those records will be the 358 * preferred target for the NDEF_DISCOVERED intent, in 359 * the order in which they appear in the {@link NdefMessage}. 360 * <p> 361 * If none of the applications are installed on the device, 362 * a Market link will be opened to the first application. 363 * <p> 364 * Note that Android application records do not overrule 365 * applications that have called {@link NfcAdapter#enableForegroundDispatch}. 366 * @hide 367 */ 368 // TODO unhide for ICS 369 // TODO recheck javadoc - should mention this works from ICS only 370 public static NdefRecord createApplicationRecord(String packageName) { 371 return new NdefRecord(TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, new byte[] {}, 372 packageName.getBytes(Charsets.US_ASCII)); 373 } 374 375 /** 376 * Creates an NDEF record of well known type URI. 377 */ 378 public static NdefRecord createUri(Uri uri) { 379 return createUri(uri.toString()); 380 } 381 382 /** 383 * Creates an NDEF record of well known type URI. 384 */ 385 public static NdefRecord createUri(String uriString) { 386 byte prefix = 0x0; 387 for (int i = 1; i < URI_PREFIX_MAP.length; i++) { 388 if (uriString.startsWith(URI_PREFIX_MAP[i])) { 389 prefix = (byte) i; 390 uriString = uriString.substring(URI_PREFIX_MAP[i].length()); 391 break; 392 } 393 } 394 byte[] uriBytes = uriString.getBytes(Charsets.UTF_8); 395 byte[] recordBytes = new byte[uriBytes.length + 1]; 396 recordBytes[0] = prefix; 397 System.arraycopy(uriBytes, 0, recordBytes, 1, uriBytes.length); 398 return new NdefRecord(TNF_WELL_KNOWN, RTD_URI, new byte[0], recordBytes); 399 } 400 401 private static byte[] concat(byte[]... arrays) { 402 int length = 0; 403 for (byte[] array : arrays) { 404 length += array.length; 405 } 406 byte[] result = new byte[length]; 407 int pos = 0; 408 for (byte[] array : arrays) { 409 System.arraycopy(array, 0, result, pos, array.length); 410 pos += array.length; 411 } 412 return result; 413 } 414 415 /** 416 * Returns this entire NDEF Record as a byte array. 417 */ 418 public byte[] toByteArray() { 419 return generate(mFlags, mTnf, mType, mId, mPayload); 420 } 421 422 public int describeContents() { 423 return 0; 424 } 425 426 public void writeToParcel(Parcel dest, int flags) { 427 dest.writeInt(mFlags); 428 dest.writeInt(mTnf); 429 dest.writeInt(mType.length); 430 dest.writeByteArray(mType); 431 dest.writeInt(mId.length); 432 dest.writeByteArray(mId); 433 dest.writeInt(mPayload.length); 434 dest.writeByteArray(mPayload); 435 } 436 437 public static final Parcelable.Creator<NdefRecord> CREATOR = 438 new Parcelable.Creator<NdefRecord>() { 439 public NdefRecord createFromParcel(Parcel in) { 440 byte flags = (byte)in.readInt(); 441 short tnf = (short)in.readInt(); 442 int typeLength = in.readInt(); 443 byte[] type = new byte[typeLength]; 444 in.readByteArray(type); 445 int idLength = in.readInt(); 446 byte[] id = new byte[idLength]; 447 in.readByteArray(id); 448 int payloadLength = in.readInt(); 449 byte[] payload = new byte[payloadLength]; 450 in.readByteArray(payload); 451 452 return new NdefRecord(tnf, type, id, payload, flags); 453 } 454 public NdefRecord[] newArray(int size) { 455 return new NdefRecord[size]; 456 } 457 }; 458 459 private native int parseNdefRecord(byte[] data); 460 private native byte[] generate(short flags, short tnf, byte[] type, byte[] id, byte[] data); 461} 462