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 17 package android.nfc; 18 19 import android.content.Context; 20 import android.nfc.tech.IsoDep; 21 import android.nfc.tech.MifareClassic; 22 import android.nfc.tech.MifareUltralight; 23 import android.nfc.tech.Ndef; 24 import android.nfc.tech.NdefFormatable; 25 import android.nfc.tech.NfcA; 26 import android.nfc.tech.NfcB; 27 import android.nfc.tech.NfcBarcode; 28 import android.nfc.tech.NfcF; 29 import android.nfc.tech.NfcV; 30 import android.nfc.tech.TagTechnology; 31 import android.os.Bundle; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.RemoteException; 35 36 import java.io.IOException; 37 import java.util.Arrays; 38 import java.util.HashMap; 39 40 /** 41 * Represents an NFC tag that has been discovered. 42 * <p> 43 * {@link Tag} is an immutable object that represents the state of a NFC tag at 44 * the time of discovery. It can be used as a handle to {@link TagTechnology} classes 45 * to perform advanced operations, or directly queried for its ID via {@link #getId} and the 46 * set of technologies it contains via {@link #getTechList}. Arrays passed to and 47 * returned by this class are <em>not</em> cloned, so be careful not to modify them. 48 * <p> 49 * A new tag object is created every time a tag is discovered (comes into range), even 50 * if it is the same physical tag. If a tag is removed and then returned into range, then 51 * only the most recent tag object can be successfully used to create a {@link TagTechnology}. 52 * 53 * <h3>Tag Dispatch</h3> 54 * When a tag is discovered, a {@link Tag} object is created and passed to a 55 * single activity via the {@link NfcAdapter#EXTRA_TAG} extra in an 56 * {@link android.content.Intent} via {@link Context#startActivity}. A four stage dispatch is used 57 * to select the 58 * most appropriate activity to handle the tag. The Android OS executes each stage in order, 59 * and completes dispatch as soon as a single matching activity is found. If there are multiple 60 * matching activities found at any one stage then the Android activity chooser dialog is shown 61 * to allow the user to select the activity to receive the tag. 62 * 63 * <p>The Tag dispatch mechanism was designed to give a high probability of dispatching 64 * a tag to the correct activity without showing the user an activity chooser dialog. 65 * This is important for NFC interactions because they are very transient -- if a user has to 66 * move the Android device to choose an application then the connection will likely be broken. 67 * 68 * <h4>1. Foreground activity dispatch</h4> 69 * A foreground activity that has called 70 * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} is 71 * given priority. See the documentation on 72 * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} for 73 * its usage. 74 * <h4>2. NDEF data dispatch</h4> 75 * If the tag contains NDEF data the system inspects the first {@link NdefRecord} in the first 76 * {@link NdefMessage}. If the record is a URI, SmartPoster, or MIME data 77 * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_NDEF_DISCOVERED}. For URI 78 * and SmartPoster records the URI is put into the intent's data field. For MIME records the MIME 79 * type is put in the intent's type field. This allows activities to register to be launched only 80 * when data they know how to handle is present on a tag. This is the preferred method of handling 81 * data on a tag since NDEF data can be stored on many types of tags and doesn't depend on a 82 * specific tag technology. 83 * See {@link NfcAdapter#ACTION_NDEF_DISCOVERED} for more detail. If the tag does not contain 84 * NDEF data, or if no activity is registered 85 * for {@link NfcAdapter#ACTION_NDEF_DISCOVERED} with a matching data URI or MIME type then dispatch 86 * moves to stage 3. 87 * <h4>3. Tag Technology dispatch</h4> 88 * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_TECH_DISCOVERED} to 89 * dispatch the tag to an activity that can handle the technologies present on the tag. 90 * Technologies are defined as sub-classes of {@link TagTechnology}, see the package 91 * {@link android.nfc.tech}. The Android OS looks for an activity that can handle one or 92 * more technologies in the tag. See {@link NfcAdapter#ACTION_TECH_DISCOVERED} for more detail. 93 * <h4>4. Fall-back dispatch</h4> 94 * If no activity has been matched then {@link Context#startActivity} is called with 95 * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. This is intended as a fall-back mechanism. 96 * See {@link NfcAdapter#ACTION_TAG_DISCOVERED}. 97 * 98 * <h3>NFC Tag Background</h3> 99 * An NFC tag is a passive NFC device, powered by the NFC field of this Android device while 100 * it is in range. Tag's can come in many forms, such as stickers, cards, key fobs, or 101 * even embedded in a more sophisticated device. 102 * <p> 103 * Tags can have a wide range of capabilities. Simple tags just offer read/write semantics, 104 * and contain some one time 105 * programmable areas to make read-only. More complex tags offer math operations 106 * and per-sector access control and authentication. The most sophisticated tags 107 * contain operating environments allowing complex interactions with the 108 * code executing on the tag. Use {@link TagTechnology} classes to access a broad 109 * range of capabilities available in NFC tags. 110 * <p> 111 */ 112 public final class Tag implements Parcelable { 113 final byte[] mId; 114 final int[] mTechList; 115 final String[] mTechStringList; 116 final Bundle[] mTechExtras; 117 final int mServiceHandle; // for use by NFC service, 0 indicates a mock 118 final INfcTag mTagService; // interface to NFC service, will be null if mock tag 119 120 int mConnectedTechnology; 121 122 /** 123 * Hidden constructor to be used by NFC service and internal classes. 124 * @hide 125 */ 126 public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle, 127 INfcTag tagService) { 128 if (techList == null) { 129 throw new IllegalArgumentException("rawTargets cannot be null"); 130 } 131 mId = id; 132 mTechList = Arrays.copyOf(techList, techList.length); 133 mTechStringList = generateTechStringList(techList); 134 // Ensure mTechExtras is as long as mTechList 135 mTechExtras = Arrays.copyOf(techListExtras, techList.length); 136 mServiceHandle = serviceHandle; 137 mTagService = tagService; 138 139 mConnectedTechnology = -1; 140 } 141 142 /** 143 * Construct a mock Tag. 144 * <p>This is an application constructed tag, so NfcAdapter methods on this Tag may fail 145 * with {@link IllegalArgumentException} since it does not represent a physical Tag. 146 * <p>This constructor might be useful for mock testing. 147 * @param id The tag identifier, can be null 148 * @param techList must not be null 149 * @return freshly constructed tag 150 * @hide 151 */ 152 public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras) { 153 // set serviceHandle to 0 and tagService to null to indicate mock tag 154 return new Tag(id, techList, techListExtras, 0, null); 155 } 156 157 private String[] generateTechStringList(int[] techList) { 158 final int size = techList.length; 159 String[] strings = new String[size]; 160 for (int i = 0; i < size; i++) { 161 switch (techList[i]) { 162 case TagTechnology.ISO_DEP: 163 strings[i] = IsoDep.class.getName(); 164 break; 165 case TagTechnology.MIFARE_CLASSIC: 166 strings[i] = MifareClassic.class.getName(); 167 break; 168 case TagTechnology.MIFARE_ULTRALIGHT: 169 strings[i] = MifareUltralight.class.getName(); 170 break; 171 case TagTechnology.NDEF: 172 strings[i] = Ndef.class.getName(); 173 break; 174 case TagTechnology.NDEF_FORMATABLE: 175 strings[i] = NdefFormatable.class.getName(); 176 break; 177 case TagTechnology.NFC_A: 178 strings[i] = NfcA.class.getName(); 179 break; 180 case TagTechnology.NFC_B: 181 strings[i] = NfcB.class.getName(); 182 break; 183 case TagTechnology.NFC_F: 184 strings[i] = NfcF.class.getName(); 185 break; 186 case TagTechnology.NFC_V: 187 strings[i] = NfcV.class.getName(); 188 break; 189 case TagTechnology.NFC_BARCODE: 190 strings[i] = NfcBarcode.class.getName(); 191 break; 192 default: 193 throw new IllegalArgumentException("Unknown tech type " + techList[i]); 194 } 195 } 196 return strings; 197 } 198 199 static int[] getTechCodesFromStrings(String[] techStringList) throws IllegalArgumentException { 200 if (techStringList == null) { 201 throw new IllegalArgumentException("List cannot be null"); 202 } 203 int[] techIntList = new int[techStringList.length]; 204 HashMap<String, Integer> stringToCodeMap = getTechStringToCodeMap(); 205 for (int i = 0; i < techStringList.length; i++) { 206 Integer code = stringToCodeMap.get(techStringList[i]); 207 208 if (code == null) { 209 throw new IllegalArgumentException("Unknown tech type " + techStringList[i]); 210 } 211 212 techIntList[i] = code.intValue(); 213 } 214 return techIntList; 215 } 216 217 private static HashMap<String, Integer> getTechStringToCodeMap() { 218 HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>(); 219 220 techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP); 221 techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC); 222 techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT); 223 techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF); 224 techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE); 225 techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A); 226 techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B); 227 techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F); 228 techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V); 229 techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE); 230 231 return techStringToCodeMap; 232 } 233 234 /** 235 * For use by NfcService only. 236 * @hide 237 */ 238 public int getServiceHandle() { 239 return mServiceHandle; 240 } 241 242 /** 243 * For use by NfcService only. 244 * @hide 245 */ 246 public int[] getTechCodeList() { 247 return mTechList; 248 } 249 250 /** 251 * Get the Tag Identifier (if it has one). 252 * <p>The tag identifier is a low level serial number, used for anti-collision 253 * and identification. 254 * <p> Most tags have a stable unique identifier 255 * (UID), but some tags will generate a random ID every time they are discovered 256 * (RID), and there are some tags with no ID at all (the byte array will be zero-sized). 257 * <p> The size and format of an ID is specific to the RF technology used by the tag. 258 * <p> This function retrieves the ID as determined at discovery time, and does not 259 * perform any further RF communication or block. 260 * @return ID as byte array, never null 261 */ 262 public byte[] getId() { 263 return mId; 264 } 265 266 /** 267 * Get the technologies available in this tag, as fully qualified class names. 268 * <p> 269 * A technology is an implementation of the {@link TagTechnology} interface, 270 * and can be instantiated by calling the static <code>get(Tag)</code> 271 * method on the implementation with this Tag. The {@link TagTechnology} 272 * object can then be used to perform advanced, technology-specific operations on a tag. 273 * <p> 274 * Android defines a mandatory set of technologies that must be correctly 275 * enumerated by all Android NFC devices, and an optional 276 * set of proprietary technologies. 277 * See {@link TagTechnology} for more details. 278 * <p> 279 * The ordering of the returned array is undefined and should not be relied upon. 280 * @return an array of fully-qualified {@link TagTechnology} class-names. 281 */ 282 public String[] getTechList() { 283 return mTechStringList; 284 } 285 286 /** 287 * Rediscover the technologies available on this tag. 288 * <p> 289 * The technologies that are available on a tag may change due to 290 * operations being performed on a tag. For example, formatting a 291 * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover} 292 * method reenumerates the available technologies on the tag 293 * and returns a new {@link Tag} object containing these technologies. 294 * <p> 295 * You may not be connected to any of this {@link Tag}'s technologies 296 * when calling this method. 297 * This method guarantees that you will be returned the same Tag 298 * if it is still in the field. 299 * <p>May cause RF activity and may block. Must not be called 300 * from the main application thread. A blocked call will be canceled with 301 * {@link IOException} by calling {@link #close} from another thread. 302 * <p>Does not remove power from the RF field, so a tag having a random 303 * ID should not change its ID. 304 * @return the rediscovered tag object. 305 * @throws IOException if the tag cannot be rediscovered 306 * @hide 307 */ 308 // TODO See if we need TagLostException 309 // TODO Unhide for ICS 310 // TODO Update documentation to make sure it matches with the final 311 // implementation. 312 public Tag rediscover() throws IOException { 313 if (getConnectedTechnology() != -1) { 314 throw new IllegalStateException("Close connection to the technology first!"); 315 } 316 317 if (mTagService == null) { 318 throw new IOException("Mock tags don't support this operation."); 319 } 320 try { 321 Tag newTag = mTagService.rediscover(getServiceHandle()); 322 if (newTag != null) { 323 return newTag; 324 } else { 325 throw new IOException("Failed to rediscover tag"); 326 } 327 } catch (RemoteException e) { 328 throw new IOException("NFC service dead"); 329 } 330 } 331 332 333 /** @hide */ 334 public boolean hasTech(int techType) { 335 for (int tech : mTechList) { 336 if (tech == techType) return true; 337 } 338 return false; 339 } 340 341 /** @hide */ 342 public Bundle getTechExtras(int tech) { 343 int pos = -1; 344 for (int idx = 0; idx < mTechList.length; idx++) { 345 if (mTechList[idx] == tech) { 346 pos = idx; 347 break; 348 } 349 } 350 if (pos < 0) { 351 return null; 352 } 353 354 return mTechExtras[pos]; 355 } 356 357 /** @hide */ 358 public INfcTag getTagService() { 359 return mTagService; 360 } 361 362 /** 363 * Human-readable description of the tag, for debugging. 364 */ 365 @Override 366 public String toString() { 367 StringBuilder sb = new StringBuilder("TAG: Tech ["); 368 String[] techList = getTechList(); 369 int length = techList.length; 370 for (int i = 0; i < length; i++) { 371 sb.append(techList[i]); 372 if (i < length - 1) { 373 sb.append(", "); 374 } 375 } 376 sb.append("]"); 377 return sb.toString(); 378 } 379 380 /*package*/ static byte[] readBytesWithNull(Parcel in) { 381 int len = in.readInt(); 382 byte[] result = null; 383 if (len >= 0) { 384 result = new byte[len]; 385 in.readByteArray(result); 386 } 387 return result; 388 } 389 390 /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) { 391 if (b == null) { 392 out.writeInt(-1); 393 return; 394 } 395 out.writeInt(b.length); 396 out.writeByteArray(b); 397 } 398 399 @Override 400 public int describeContents() { 401 return 0; 402 } 403 404 @Override 405 public void writeToParcel(Parcel dest, int flags) { 406 // Null mTagService means this is a mock tag 407 int isMock = (mTagService == null)?1:0; 408 409 writeBytesWithNull(dest, mId); 410 dest.writeInt(mTechList.length); 411 dest.writeIntArray(mTechList); 412 dest.writeTypedArray(mTechExtras, 0); 413 dest.writeInt(mServiceHandle); 414 dest.writeInt(isMock); 415 if (isMock == 0) { 416 dest.writeStrongBinder(mTagService.asBinder()); 417 } 418 } 419 420 public static final Parcelable.Creator<Tag> CREATOR = 421 new Parcelable.Creator<Tag>() { 422 @Override 423 public Tag createFromParcel(Parcel in) { 424 INfcTag tagService; 425 426 // Tag fields 427 byte[] id = Tag.readBytesWithNull(in); 428 int[] techList = new int[in.readInt()]; 429 in.readIntArray(techList); 430 Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR); 431 int serviceHandle = in.readInt(); 432 int isMock = in.readInt(); 433 if (isMock == 0) { 434 tagService = INfcTag.Stub.asInterface(in.readStrongBinder()); 435 } 436 else { 437 tagService = null; 438 } 439 440 return new Tag(id, techList, techExtras, serviceHandle, tagService); 441 } 442 443 @Override 444 public Tag[] newArray(int size) { 445 return new Tag[size]; 446 } 447 }; 448 449 /** 450 * For internal use only. 451 * 452 * @hide 453 */ 454 public synchronized void setConnectedTechnology(int technology) { 455 if (mConnectedTechnology == -1) { 456 mConnectedTechnology = technology; 457 } else { 458 throw new IllegalStateException("Close other technology first!"); 459 } 460 } 461 462 /** 463 * For internal use only. 464 * 465 * @hide 466 */ 467 public int getConnectedTechnology() { 468 return mConnectedTechnology; 469 } 470 471 /** 472 * For internal use only. 473 * 474 * @hide 475 */ 476 public void setTechnologyDisconnected() { 477 mConnectedTechnology = -1; 478 } 479 } 480