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