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