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
17package android.nfc;
18
19import android.content.Context;
20import android.nfc.tech.IsoDep;
21import android.nfc.tech.MifareClassic;
22import android.nfc.tech.MifareUltralight;
23import android.nfc.tech.Ndef;
24import android.nfc.tech.NdefFormatable;
25import android.nfc.tech.NfcA;
26import android.nfc.tech.NfcB;
27import android.nfc.tech.NfcBarcode;
28import android.nfc.tech.NfcF;
29import android.nfc.tech.NfcV;
30import android.nfc.tech.TagTechnology;
31import android.os.Bundle;
32import android.os.Parcel;
33import android.os.Parcelable;
34import android.os.RemoteException;
35
36import java.io.IOException;
37import java.util.Arrays;
38import 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 */
112public 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     */
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     */
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
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
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
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     */
238    public int getServiceHandle() {
239        return mServiceHandle;
240    }
241
242    /**
243     * For use by NfcService only.
244     * @hide
245     */
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     */
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     */
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.
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 */
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 */
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 */
358    public INfcTag getTagService() {
359        return mTagService;
360    }
361
362    /**
363     * Human-readable description of the tag, for debugging.
364     */
365    @Override
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
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
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
400    public int describeContents() {
401        return 0;
402    }
403
404    @Override
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     */
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     */
467    public int getConnectedTechnology() {
468        return mConnectedTechnology;
469    }
470
471    /**
472     * For internal use only.
473     *
474     * @hide
475     */
476    public void setTechnologyDisconnected() {
477        mConnectedTechnology = -1;
478    }
479}
480