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