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