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