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