content: /*
   * 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.annotation.UnsupportedAppUsage;
  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 {
      @UnsupportedAppUsage
      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
       */
      @UnsupportedAppUsage
      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 */
      @UnsupportedAppUsage
      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 @android.annotation.NonNull 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;
      }
  }
  