Home | History | Annotate | Download | only in nfc
      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.content.Context;
     20 import android.nfc.tech.IsoDep;
     21 import android.nfc.tech.MifareClassic;
     22 import android.nfc.tech.MifareUltralight;
     23 import android.nfc.tech.Ndef;
     24 import android.nfc.tech.NdefFormatable;
     25 import android.nfc.tech.NfcA;
     26 import android.nfc.tech.NfcB;
     27 import android.nfc.tech.NfcBarcode;
     28 import android.nfc.tech.NfcF;
     29 import android.nfc.tech.NfcV;
     30 import android.nfc.tech.TagTechnology;
     31 import android.os.Bundle;
     32 import android.os.Parcel;
     33 import android.os.Parcelable;
     34 import android.os.RemoteException;
     35 
     36 import java.io.IOException;
     37 import java.util.Arrays;
     38 import 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  */
    112 public 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