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 INfcTag mTagService; // interface to NFC service, will be null if mock tag 122 123 int mConnectedTechnology; 124 125 /** 126 * Hidden constructor to be used by NFC service and internal classes. 127 * @hide 128 */ Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle, INfcTag tagService)129 public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle, 130 INfcTag tagService) { 131 if (techList == null) { 132 throw new IllegalArgumentException("rawTargets cannot be null"); 133 } 134 mId = id; 135 mTechList = Arrays.copyOf(techList, techList.length); 136 mTechStringList = generateTechStringList(techList); 137 // Ensure mTechExtras is as long as mTechList 138 mTechExtras = Arrays.copyOf(techListExtras, techList.length); 139 mServiceHandle = serviceHandle; 140 mTagService = tagService; 141 142 mConnectedTechnology = -1; 143 } 144 145 /** 146 * Construct a mock Tag. 147 * <p>This is an application constructed tag, so NfcAdapter methods on this Tag may fail 148 * with {@link IllegalArgumentException} since it does not represent a physical Tag. 149 * <p>This constructor might be useful for mock testing. 150 * @param id The tag identifier, can be null 151 * @param techList must not be null 152 * @return freshly constructed tag 153 * @hide 154 */ createMockTag(byte[] id, int[] techList, Bundle[] techListExtras)155 public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras) { 156 // set serviceHandle to 0 and tagService to null to indicate mock tag 157 return new Tag(id, techList, techListExtras, 0, null); 158 } 159 generateTechStringList(int[] techList)160 private String[] generateTechStringList(int[] techList) { 161 final int size = techList.length; 162 String[] strings = new String[size]; 163 for (int i = 0; i < size; i++) { 164 switch (techList[i]) { 165 case TagTechnology.ISO_DEP: 166 strings[i] = IsoDep.class.getName(); 167 break; 168 case TagTechnology.MIFARE_CLASSIC: 169 strings[i] = MifareClassic.class.getName(); 170 break; 171 case TagTechnology.MIFARE_ULTRALIGHT: 172 strings[i] = MifareUltralight.class.getName(); 173 break; 174 case TagTechnology.NDEF: 175 strings[i] = Ndef.class.getName(); 176 break; 177 case TagTechnology.NDEF_FORMATABLE: 178 strings[i] = NdefFormatable.class.getName(); 179 break; 180 case TagTechnology.NFC_A: 181 strings[i] = NfcA.class.getName(); 182 break; 183 case TagTechnology.NFC_B: 184 strings[i] = NfcB.class.getName(); 185 break; 186 case TagTechnology.NFC_F: 187 strings[i] = NfcF.class.getName(); 188 break; 189 case TagTechnology.NFC_V: 190 strings[i] = NfcV.class.getName(); 191 break; 192 case TagTechnology.NFC_BARCODE: 193 strings[i] = NfcBarcode.class.getName(); 194 break; 195 default: 196 throw new IllegalArgumentException("Unknown tech type " + techList[i]); 197 } 198 } 199 return strings; 200 } 201 getTechCodesFromStrings(String[] techStringList)202 static int[] getTechCodesFromStrings(String[] techStringList) throws IllegalArgumentException { 203 if (techStringList == null) { 204 throw new IllegalArgumentException("List cannot be null"); 205 } 206 int[] techIntList = new int[techStringList.length]; 207 HashMap<String, Integer> stringToCodeMap = getTechStringToCodeMap(); 208 for (int i = 0; i < techStringList.length; i++) { 209 Integer code = stringToCodeMap.get(techStringList[i]); 210 211 if (code == null) { 212 throw new IllegalArgumentException("Unknown tech type " + techStringList[i]); 213 } 214 215 techIntList[i] = code.intValue(); 216 } 217 return techIntList; 218 } 219 getTechStringToCodeMap()220 private static HashMap<String, Integer> getTechStringToCodeMap() { 221 HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>(); 222 223 techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP); 224 techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC); 225 techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT); 226 techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF); 227 techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE); 228 techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A); 229 techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B); 230 techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F); 231 techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V); 232 techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE); 233 234 return techStringToCodeMap; 235 } 236 237 /** 238 * For use by NfcService only. 239 * @hide 240 */ 241 @UnsupportedAppUsage getServiceHandle()242 public int getServiceHandle() { 243 return mServiceHandle; 244 } 245 246 /** 247 * For use by NfcService only. 248 * @hide 249 */ getTechCodeList()250 public int[] getTechCodeList() { 251 return mTechList; 252 } 253 254 /** 255 * Get the Tag Identifier (if it has one). 256 * <p>The tag identifier is a low level serial number, used for anti-collision 257 * and identification. 258 * <p> Most tags have a stable unique identifier 259 * (UID), but some tags will generate a random ID every time they are discovered 260 * (RID), and there are some tags with no ID at all (the byte array will be zero-sized). 261 * <p> The size and format of an ID is specific to the RF technology used by the tag. 262 * <p> This function retrieves the ID as determined at discovery time, and does not 263 * perform any further RF communication or block. 264 * @return ID as byte array, never null 265 */ getId()266 public byte[] getId() { 267 return mId; 268 } 269 270 /** 271 * Get the technologies available in this tag, as fully qualified class names. 272 * <p> 273 * A technology is an implementation of the {@link TagTechnology} interface, 274 * and can be instantiated by calling the static <code>get(Tag)</code> 275 * method on the implementation with this Tag. The {@link TagTechnology} 276 * object can then be used to perform advanced, technology-specific operations on a tag. 277 * <p> 278 * Android defines a mandatory set of technologies that must be correctly 279 * enumerated by all Android NFC devices, and an optional 280 * set of proprietary technologies. 281 * See {@link TagTechnology} for more details. 282 * <p> 283 * The ordering of the returned array is undefined and should not be relied upon. 284 * @return an array of fully-qualified {@link TagTechnology} class-names. 285 */ getTechList()286 public String[] getTechList() { 287 return mTechStringList; 288 } 289 290 /** 291 * Rediscover the technologies available on this tag. 292 * <p> 293 * The technologies that are available on a tag may change due to 294 * operations being performed on a tag. For example, formatting a 295 * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover} 296 * method reenumerates the available technologies on the tag 297 * and returns a new {@link Tag} object containing these technologies. 298 * <p> 299 * You may not be connected to any of this {@link Tag}'s technologies 300 * when calling this method. 301 * This method guarantees that you will be returned the same Tag 302 * if it is still in the field. 303 * <p>May cause RF activity and may block. Must not be called 304 * from the main application thread. A blocked call will be canceled with 305 * {@link IOException} by calling {@link #close} from another thread. 306 * <p>Does not remove power from the RF field, so a tag having a random 307 * ID should not change its ID. 308 * @return the rediscovered tag object. 309 * @throws IOException if the tag cannot be rediscovered 310 * @hide 311 */ 312 // TODO See if we need TagLostException 313 // TODO Unhide for ICS 314 // TODO Update documentation to make sure it matches with the final 315 // implementation. rediscover()316 public Tag rediscover() throws IOException { 317 if (getConnectedTechnology() != -1) { 318 throw new IllegalStateException("Close connection to the technology first!"); 319 } 320 321 if (mTagService == null) { 322 throw new IOException("Mock tags don't support this operation."); 323 } 324 try { 325 Tag newTag = mTagService.rediscover(getServiceHandle()); 326 if (newTag != null) { 327 return newTag; 328 } else { 329 throw new IOException("Failed to rediscover tag"); 330 } 331 } catch (RemoteException e) { 332 throw new IOException("NFC service dead"); 333 } 334 } 335 336 337 /** @hide */ hasTech(int techType)338 public boolean hasTech(int techType) { 339 for (int tech : mTechList) { 340 if (tech == techType) return true; 341 } 342 return false; 343 } 344 345 /** @hide */ getTechExtras(int tech)346 public Bundle getTechExtras(int tech) { 347 int pos = -1; 348 for (int idx = 0; idx < mTechList.length; idx++) { 349 if (mTechList[idx] == tech) { 350 pos = idx; 351 break; 352 } 353 } 354 if (pos < 0) { 355 return null; 356 } 357 358 return mTechExtras[pos]; 359 } 360 361 /** @hide */ 362 @UnsupportedAppUsage getTagService()363 public INfcTag getTagService() { 364 return mTagService; 365 } 366 367 /** 368 * Human-readable description of the tag, for debugging. 369 */ 370 @Override toString()371 public String toString() { 372 StringBuilder sb = new StringBuilder("TAG: Tech ["); 373 String[] techList = getTechList(); 374 int length = techList.length; 375 for (int i = 0; i < length; i++) { 376 sb.append(techList[i]); 377 if (i < length - 1) { 378 sb.append(", "); 379 } 380 } 381 sb.append("]"); 382 return sb.toString(); 383 } 384 readBytesWithNull(Parcel in)385 /*package*/ static byte[] readBytesWithNull(Parcel in) { 386 int len = in.readInt(); 387 byte[] result = null; 388 if (len >= 0) { 389 result = new byte[len]; 390 in.readByteArray(result); 391 } 392 return result; 393 } 394 writeBytesWithNull(Parcel out, byte[] b)395 /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) { 396 if (b == null) { 397 out.writeInt(-1); 398 return; 399 } 400 out.writeInt(b.length); 401 out.writeByteArray(b); 402 } 403 404 @Override describeContents()405 public int describeContents() { 406 return 0; 407 } 408 409 @Override writeToParcel(Parcel dest, int flags)410 public void writeToParcel(Parcel dest, int flags) { 411 // Null mTagService means this is a mock tag 412 int isMock = (mTagService == null)?1:0; 413 414 writeBytesWithNull(dest, mId); 415 dest.writeInt(mTechList.length); 416 dest.writeIntArray(mTechList); 417 dest.writeTypedArray(mTechExtras, 0); 418 dest.writeInt(mServiceHandle); 419 dest.writeInt(isMock); 420 if (isMock == 0) { 421 dest.writeStrongBinder(mTagService.asBinder()); 422 } 423 } 424 425 public static final @android.annotation.NonNull Parcelable.Creator<Tag> CREATOR = 426 new Parcelable.Creator<Tag>() { 427 @Override 428 public Tag createFromParcel(Parcel in) { 429 INfcTag tagService; 430 431 // Tag fields 432 byte[] id = Tag.readBytesWithNull(in); 433 int[] techList = new int[in.readInt()]; 434 in.readIntArray(techList); 435 Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR); 436 int serviceHandle = in.readInt(); 437 int isMock = in.readInt(); 438 if (isMock == 0) { 439 tagService = INfcTag.Stub.asInterface(in.readStrongBinder()); 440 } 441 else { 442 tagService = null; 443 } 444 445 return new Tag(id, techList, techExtras, serviceHandle, tagService); 446 } 447 448 @Override 449 public Tag[] newArray(int size) { 450 return new Tag[size]; 451 } 452 }; 453 454 /** 455 * For internal use only. 456 * 457 * @hide 458 */ setConnectedTechnology(int technology)459 public synchronized boolean setConnectedTechnology(int technology) { 460 if (mConnectedTechnology != -1) { 461 return false; 462 } 463 mConnectedTechnology = technology; 464 return true; 465 } 466 467 /** 468 * For internal use only. 469 * 470 * @hide 471 */ getConnectedTechnology()472 public int getConnectedTechnology() { 473 return mConnectedTechnology; 474 } 475 476 /** 477 * For internal use only. 478 * 479 * @hide 480 */ setTechnologyDisconnected()481 public void setTechnologyDisconnected() { 482 mConnectedTechnology = -1; 483 } 484 } 485