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