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