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