/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.nfc; import android.content.Context; import android.nfc.tech.IsoDep; import android.nfc.tech.MifareClassic; import android.nfc.tech.MifareUltralight; import android.nfc.tech.Ndef; import android.nfc.tech.NdefFormatable; import android.nfc.tech.NfcA; import android.nfc.tech.NfcB; import android.nfc.tech.NfcBarcode; import android.nfc.tech.NfcF; import android.nfc.tech.NfcV; import android.nfc.tech.TagTechnology; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; /** * Represents an NFC tag that has been discovered. *

* {@link Tag} is an immutable object that represents the state of a NFC tag at * the time of discovery. It can be used as a handle to {@link TagTechnology} classes * to perform advanced operations, or directly queried for its ID via {@link #getId} and the * set of technologies it contains via {@link #getTechList}. Arrays passed to and * returned by this class are not cloned, so be careful not to modify them. *

* A new tag object is created every time a tag is discovered (comes into range), even * if it is the same physical tag. If a tag is removed and then returned into range, then * only the most recent tag object can be successfully used to create a {@link TagTechnology}. * *

Tag Dispatch

* When a tag is discovered, a {@link Tag} object is created and passed to a * single activity via the {@link NfcAdapter#EXTRA_TAG} extra in an * {@link android.content.Intent} via {@link Context#startActivity}. A four stage dispatch is used * to select the * most appropriate activity to handle the tag. The Android OS executes each stage in order, * and completes dispatch as soon as a single matching activity is found. If there are multiple * matching activities found at any one stage then the Android activity chooser dialog is shown * to allow the user to select the activity to receive the tag. * *

The Tag dispatch mechanism was designed to give a high probability of dispatching * a tag to the correct activity without showing the user an activity chooser dialog. * This is important for NFC interactions because they are very transient -- if a user has to * move the Android device to choose an application then the connection will likely be broken. * *

1. Foreground activity dispatch

* A foreground activity that has called * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} is * given priority. See the documentation on * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} for * its usage. *

2. NDEF data dispatch

* If the tag contains NDEF data the system inspects the first {@link NdefRecord} in the first * {@link NdefMessage}. If the record is a URI, SmartPoster, or MIME data * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_NDEF_DISCOVERED}. For URI * and SmartPoster records the URI is put into the intent's data field. For MIME records the MIME * type is put in the intent's type field. This allows activities to register to be launched only * when data they know how to handle is present on a tag. This is the preferred method of handling * data on a tag since NDEF data can be stored on many types of tags and doesn't depend on a * specific tag technology. * See {@link NfcAdapter#ACTION_NDEF_DISCOVERED} for more detail. If the tag does not contain * NDEF data, or if no activity is registered * for {@link NfcAdapter#ACTION_NDEF_DISCOVERED} with a matching data URI or MIME type then dispatch * moves to stage 3. *

3. Tag Technology dispatch

* {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_TECH_DISCOVERED} to * dispatch the tag to an activity that can handle the technologies present on the tag. * Technologies are defined as sub-classes of {@link TagTechnology}, see the package * {@link android.nfc.tech}. The Android OS looks for an activity that can handle one or * more technologies in the tag. See {@link NfcAdapter#ACTION_TECH_DISCOVERED} for more detail. *

4. Fall-back dispatch

* If no activity has been matched then {@link Context#startActivity} is called with * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. This is intended as a fall-back mechanism. * See {@link NfcAdapter#ACTION_TAG_DISCOVERED}. * *

NFC Tag Background

* An NFC tag is a passive NFC device, powered by the NFC field of this Android device while * it is in range. Tag's can come in many forms, such as stickers, cards, key fobs, or * even embedded in a more sophisticated device. *

* Tags can have a wide range of capabilities. Simple tags just offer read/write semantics, * and contain some one time * programmable areas to make read-only. More complex tags offer math operations * and per-sector access control and authentication. The most sophisticated tags * contain operating environments allowing complex interactions with the * code executing on the tag. Use {@link TagTechnology} classes to access a broad * range of capabilities available in NFC tags. *

*/ public final class Tag implements Parcelable { final byte[] mId; final int[] mTechList; final String[] mTechStringList; final Bundle[] mTechExtras; final int mServiceHandle; // for use by NFC service, 0 indicates a mock final INfcTag mTagService; // interface to NFC service, will be null if mock tag int mConnectedTechnology; /** * Hidden constructor to be used by NFC service and internal classes. * @hide */ public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle, INfcTag tagService) { if (techList == null) { throw new IllegalArgumentException("rawTargets cannot be null"); } mId = id; mTechList = Arrays.copyOf(techList, techList.length); mTechStringList = generateTechStringList(techList); // Ensure mTechExtras is as long as mTechList mTechExtras = Arrays.copyOf(techListExtras, techList.length); mServiceHandle = serviceHandle; mTagService = tagService; mConnectedTechnology = -1; } /** * Construct a mock Tag. *

This is an application constructed tag, so NfcAdapter methods on this Tag may fail * with {@link IllegalArgumentException} since it does not represent a physical Tag. *

This constructor might be useful for mock testing. * @param id The tag identifier, can be null * @param techList must not be null * @return freshly constructed tag * @hide */ public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras) { // set serviceHandle to 0 and tagService to null to indicate mock tag return new Tag(id, techList, techListExtras, 0, null); } private String[] generateTechStringList(int[] techList) { final int size = techList.length; String[] strings = new String[size]; for (int i = 0; i < size; i++) { switch (techList[i]) { case TagTechnology.ISO_DEP: strings[i] = IsoDep.class.getName(); break; case TagTechnology.MIFARE_CLASSIC: strings[i] = MifareClassic.class.getName(); break; case TagTechnology.MIFARE_ULTRALIGHT: strings[i] = MifareUltralight.class.getName(); break; case TagTechnology.NDEF: strings[i] = Ndef.class.getName(); break; case TagTechnology.NDEF_FORMATABLE: strings[i] = NdefFormatable.class.getName(); break; case TagTechnology.NFC_A: strings[i] = NfcA.class.getName(); break; case TagTechnology.NFC_B: strings[i] = NfcB.class.getName(); break; case TagTechnology.NFC_F: strings[i] = NfcF.class.getName(); break; case TagTechnology.NFC_V: strings[i] = NfcV.class.getName(); break; case TagTechnology.NFC_BARCODE: strings[i] = NfcBarcode.class.getName(); break; default: throw new IllegalArgumentException("Unknown tech type " + techList[i]); } } return strings; } static int[] getTechCodesFromStrings(String[] techStringList) throws IllegalArgumentException { if (techStringList == null) { throw new IllegalArgumentException("List cannot be null"); } int[] techIntList = new int[techStringList.length]; HashMap stringToCodeMap = getTechStringToCodeMap(); for (int i = 0; i < techStringList.length; i++) { Integer code = stringToCodeMap.get(techStringList[i]); if (code == null) { throw new IllegalArgumentException("Unknown tech type " + techStringList[i]); } techIntList[i] = code.intValue(); } return techIntList; } private static HashMap getTechStringToCodeMap() { HashMap techStringToCodeMap = new HashMap(); techStringToCodeMap.put(IsoDep.class.getName(), TagTechnology.ISO_DEP); techStringToCodeMap.put(MifareClassic.class.getName(), TagTechnology.MIFARE_CLASSIC); techStringToCodeMap.put(MifareUltralight.class.getName(), TagTechnology.MIFARE_ULTRALIGHT); techStringToCodeMap.put(Ndef.class.getName(), TagTechnology.NDEF); techStringToCodeMap.put(NdefFormatable.class.getName(), TagTechnology.NDEF_FORMATABLE); techStringToCodeMap.put(NfcA.class.getName(), TagTechnology.NFC_A); techStringToCodeMap.put(NfcB.class.getName(), TagTechnology.NFC_B); techStringToCodeMap.put(NfcF.class.getName(), TagTechnology.NFC_F); techStringToCodeMap.put(NfcV.class.getName(), TagTechnology.NFC_V); techStringToCodeMap.put(NfcBarcode.class.getName(), TagTechnology.NFC_BARCODE); return techStringToCodeMap; } /** * For use by NfcService only. * @hide */ public int getServiceHandle() { return mServiceHandle; } /** * For use by NfcService only. * @hide */ public int[] getTechCodeList() { return mTechList; } /** * Get the Tag Identifier (if it has one). *

The tag identifier is a low level serial number, used for anti-collision * and identification. *

Most tags have a stable unique identifier * (UID), but some tags will generate a random ID every time they are discovered * (RID), and there are some tags with no ID at all (the byte array will be zero-sized). *

The size and format of an ID is specific to the RF technology used by the tag. *

This function retrieves the ID as determined at discovery time, and does not * perform any further RF communication or block. * @return ID as byte array, never null */ public byte[] getId() { return mId; } /** * Get the technologies available in this tag, as fully qualified class names. *

* A technology is an implementation of the {@link TagTechnology} interface, * and can be instantiated by calling the static get(Tag) * method on the implementation with this Tag. The {@link TagTechnology} * object can then be used to perform advanced, technology-specific operations on a tag. *

* Android defines a mandatory set of technologies that must be correctly * enumerated by all Android NFC devices, and an optional * set of proprietary technologies. * See {@link TagTechnology} for more details. *

* The ordering of the returned array is undefined and should not be relied upon. * @return an array of fully-qualified {@link TagTechnology} class-names. */ public String[] getTechList() { return mTechStringList; } /** * Rediscover the technologies available on this tag. *

* The technologies that are available on a tag may change due to * operations being performed on a tag. For example, formatting a * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover} * method reenumerates the available technologies on the tag * and returns a new {@link Tag} object containing these technologies. *

* You may not be connected to any of this {@link Tag}'s technologies * when calling this method. * This method guarantees that you will be returned the same Tag * if it is still in the field. *

May cause RF activity and may block. Must not be called * from the main application thread. A blocked call will be canceled with * {@link IOException} by calling {@link #close} from another thread. *

Does not remove power from the RF field, so a tag having a random * ID should not change its ID. * @return the rediscovered tag object. * @throws IOException if the tag cannot be rediscovered * @hide */ // TODO See if we need TagLostException // TODO Unhide for ICS // TODO Update documentation to make sure it matches with the final // implementation. public Tag rediscover() throws IOException { if (getConnectedTechnology() != -1) { throw new IllegalStateException("Close connection to the technology first!"); } if (mTagService == null) { throw new IOException("Mock tags don't support this operation."); } try { Tag newTag = mTagService.rediscover(getServiceHandle()); if (newTag != null) { return newTag; } else { throw new IOException("Failed to rediscover tag"); } } catch (RemoteException e) { throw new IOException("NFC service dead"); } } /** @hide */ public boolean hasTech(int techType) { for (int tech : mTechList) { if (tech == techType) return true; } return false; } /** @hide */ public Bundle getTechExtras(int tech) { int pos = -1; for (int idx = 0; idx < mTechList.length; idx++) { if (mTechList[idx] == tech) { pos = idx; break; } } if (pos < 0) { return null; } return mTechExtras[pos]; } /** @hide */ public INfcTag getTagService() { return mTagService; } /** * Human-readable description of the tag, for debugging. */ @Override public String toString() { StringBuilder sb = new StringBuilder("TAG: Tech ["); String[] techList = getTechList(); int length = techList.length; for (int i = 0; i < length; i++) { sb.append(techList[i]); if (i < length - 1) { sb.append(", "); } } sb.append("]"); return sb.toString(); } /*package*/ static byte[] readBytesWithNull(Parcel in) { int len = in.readInt(); byte[] result = null; if (len >= 0) { result = new byte[len]; in.readByteArray(result); } return result; } /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) { if (b == null) { out.writeInt(-1); return; } out.writeInt(b.length); out.writeByteArray(b); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { // Null mTagService means this is a mock tag int isMock = (mTagService == null)?1:0; writeBytesWithNull(dest, mId); dest.writeInt(mTechList.length); dest.writeIntArray(mTechList); dest.writeTypedArray(mTechExtras, 0); dest.writeInt(mServiceHandle); dest.writeInt(isMock); if (isMock == 0) { dest.writeStrongBinder(mTagService.asBinder()); } } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override public Tag createFromParcel(Parcel in) { INfcTag tagService; // Tag fields byte[] id = Tag.readBytesWithNull(in); int[] techList = new int[in.readInt()]; in.readIntArray(techList); Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR); int serviceHandle = in.readInt(); int isMock = in.readInt(); if (isMock == 0) { tagService = INfcTag.Stub.asInterface(in.readStrongBinder()); } else { tagService = null; } return new Tag(id, techList, techExtras, serviceHandle, tagService); } @Override public Tag[] newArray(int size) { return new Tag[size]; } }; /** * For internal use only. * * @hide */ public synchronized void setConnectedTechnology(int technology) { if (mConnectedTechnology == -1) { mConnectedTechnology = technology; } else { throw new IllegalStateException("Close other technology first!"); } } /** * For internal use only. * * @hide */ public int getConnectedTechnology() { return mConnectedTechnology; } /** * For internal use only. * * @hide */ public void setTechnologyDisconnected() { mConnectedTechnology = -1; } }