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