| /* |
| * 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. |
| * <p> |
| * {@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 <em>not</em> cloned, so be careful not to modify them. |
| * <p> |
| * 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}. |
| * |
| * <h3>Tag Dispatch</h3> |
| * 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. |
| * |
| * <p>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. |
| * |
| * <h4>1. Foreground activity dispatch</h4> |
| * 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. |
| * <h4>2. NDEF data dispatch</h4> |
| * 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. |
| * <h4>3. Tag Technology dispatch</h4> |
| * {@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. |
| * <h4>4. Fall-back dispatch</h4> |
| * 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}. |
| * |
| * <h3>NFC Tag Background</h3> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| */ |
| 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. |
| * <p>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. |
| * <p>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<String, Integer> 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<String, Integer> getTechStringToCodeMap() { |
| HashMap<String, Integer> techStringToCodeMap = new HashMap<String, Integer>(); |
| |
| 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). |
| * <p>The tag identifier is a low level serial number, used for anti-collision |
| * and identification. |
| * <p> 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). |
| * <p> The size and format of an ID is specific to the RF technology used by the tag. |
| * <p> 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. |
| * <p> |
| * A technology is an implementation of the {@link TagTechnology} interface, |
| * and can be instantiated by calling the static <code>get(Tag)</code> |
| * method on the implementation with this Tag. The {@link TagTechnology} |
| * object can then be used to perform advanced, technology-specific operations on a tag. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p> |
| * 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. |
| * <p>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. |
| * <p>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<Tag> CREATOR = |
| new Parcelable.Creator<Tag>() { |
| @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; |
| } |
| } |