OSDN Git Service

Add an API for querying HCE service selection mode.
authorMartijn Coenen <maco@google.com>
Thu, 29 Aug 2013 02:23:41 +0000 (19:23 -0700)
committerMartijn Coenen <maco@google.com>
Thu, 29 Aug 2013 20:23:03 +0000 (13:23 -0700)
This allows applications to differentiate between
the case where another app is the default, or the
case where the selection mode is "Ask every time".

Also, parse new requireDeviceUnlock attribute in
ApduServiceInfo.

Bug: 10262585
Change-Id: Icac508fe00054132574731532c05a1138edb24f5

api/current.txt
core/java/android/nfc/cardemulation/ApduServiceInfo.java
core/java/android/nfc/cardemulation/CardEmulationManager.java
core/java/android/nfc/cardemulation/HostApduService.java
core/java/android/provider/Settings.java

index 99d8391..b7400d4 100644 (file)
@@ -15261,6 +15261,7 @@ package android.nfc.cardemulation {
 
   public final class CardEmulationManager {
     method public static synchronized android.nfc.cardemulation.CardEmulationManager getInstance(android.nfc.NfcAdapter);
+    method public int getSelectionModeForCategory(java.lang.String);
     method public boolean isDefaultServiceForAid(android.content.ComponentName, java.lang.String);
     method public boolean isDefaultServiceForCategory(android.content.ComponentName, java.lang.String);
     field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT";
@@ -15268,6 +15269,9 @@ package android.nfc.cardemulation {
     field public static final java.lang.String CATEGORY_PAYMENT = "payment";
     field public static final java.lang.String EXTRA_CATEGORY = "category";
     field public static final java.lang.String EXTRA_SERVICE_COMPONENT = "component";
+    field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
+    field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
+    field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
   }
 
   public abstract class HostApduService extends android.app.Service {
index 3f7e3ef..b83911a 100644 (file)
@@ -75,16 +75,22 @@ public final class ApduServiceInfo implements Parcelable {
     final HashMap<String, AidGroup> mCategoryToGroup;
 
     /**
+     * Whether this service should only be started when the device is unlocked.
+     */
+    final boolean mRequiresDeviceUnlock;
+
+    /**
      * @hide
      */
     public ApduServiceInfo(ResolveInfo info, boolean onHost, String description,
-            ArrayList<AidGroup> aidGroups) {
+            ArrayList<AidGroup> aidGroups, boolean requiresUnlock) {
         this.mService = info;
         this.mDescription = description;
         this.mAidGroups = aidGroups;
         this.mAids = new ArrayList<String>();
         this.mCategoryToGroup = new HashMap<String, AidGroup>();
         this.mOnHost = onHost;
+        this.mRequiresDeviceUnlock = requiresUnlock;
         for (AidGroup aidGroup : aidGroups) {
             this.mCategoryToGroup.put(aidGroup.category, aidGroup);
             this.mAids.addAll(aidGroup.aids);
@@ -132,12 +138,16 @@ public final class ApduServiceInfo implements Parcelable {
                 mService = info;
                 mDescription = sa.getString(
                         com.android.internal.R.styleable.HostApduService_description);
+                mRequiresDeviceUnlock = sa.getBoolean(
+                        com.android.internal.R.styleable.HostApduService_requireDeviceUnlock,
+                        false);
             } else {
                 TypedArray sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.OffHostApduService);
                 mService = info;
                 mDescription = sa.getString(
                         com.android.internal.R.styleable.OffHostApduService_description);
+                mRequiresDeviceUnlock = false;
             }
 
             mAidGroups = new ArrayList<AidGroup>();
@@ -226,6 +236,10 @@ public final class ApduServiceInfo implements Parcelable {
         return mOnHost;
     }
 
+    public boolean requiresUnlock() {
+        return mRequiresDeviceUnlock;
+    }
+
     public CharSequence loadLabel(PackageManager pm) {
         return mService.loadLabel(pm);
     }
@@ -287,6 +301,7 @@ public final class ApduServiceInfo implements Parcelable {
         if (mAidGroups.size() > 0) {
             dest.writeTypedList(mAidGroups);
         }
+        dest.writeInt(mRequiresDeviceUnlock ? 1 : 0);
     };
 
     public static final Parcelable.Creator<ApduServiceInfo> CREATOR =
@@ -301,7 +316,8 @@ public final class ApduServiceInfo implements Parcelable {
             if (numGroups > 0) {
                 source.readTypedList(aidGroups, AidGroup.CREATOR);
             }
-            return new ApduServiceInfo(info, onHost, description, aidGroups);
+            boolean requiresUnlock = (source.readInt() != 0) ? true : false;
+            return new ApduServiceInfo(info, onHost, description, aidGroups, requiresUnlock);
         }
 
         @Override
index 537fded..9d60c73 100644 (file)
@@ -27,6 +27,7 @@ import android.nfc.INfcCardEmulation;
 import android.nfc.NfcAdapter;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -78,19 +79,68 @@ public final class CardEmulationManager {
      */
     public static final String CATEGORY_OTHER = "other";
 
-    static boolean sIsInitialized = false;
-    static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap();
-    static INfcCardEmulation sService;
+    /**
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, the user has set a default service for this
+     *    AID category. If a remote reader selects any of the AIDs
+     *    that the default service has registered in this category,
+     *    that service will automatically be bound to to handle
+     *    the transaction.
+     *
+     * <p>There are still cases where a service that is
+     *    not the default for a category can selected:
+     *    <p>
+     *    If a remote reader selects an AID in this category
+     *    that is not handled by the default service, and there is a set
+     *    of other services {S} that do handle this AID, the
+     *    user is asked if he wants to use any of the services in
+     *    {S} instead.
+     *    <p>
+     *    As a special case, if the size of {S} is one, containing a single service X,
+     *    and all AIDs X has registered in this category are not
+     *    registered by any other service, then X will be
+     *    selected automatically without asking the user.
+     *    <p>Example:
+     *    <ul>
+     *    <li>Service A registers AIDs "1", "2" and "3" in the category
+     *    <li>Service B registers AIDs "3" and "4" in the category
+     *    <li>Service C registers AIDs "5" and "6" in the category
+     *    </ul>
+     *    In this case, the following will happen when service A
+     *    is the default:
+     *    <ul>
+     *    <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
+     *    <li>Reader selects AID "4": the user is asked to confirm he
+     *        wants to use service B, because its AIDs overlap with service A.
+     *    <li>Reader selects AID "5" or "6": service C is invoked automatically,
+     *        because all AIDs it has asked for are only registered by C,
+     *        and there is no overlap.
+     *    </ul>
+     *
+     */
+    public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
 
     /**
-     * @hide
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, whenever an AID of this category is selected,
+     *    the user is asked which service he wants to use to handle
+     *    the transaction, even if there is only one matching service.
      */
-    public static final String PAYMENT_MODE_AUTO = "auto";
+    public static final int SELECTION_MODE_ALWAYS_ASK = 1;
 
     /**
-     * @hide
+     * Return value for {@link #getSelectionModeForCategory(String)}.
+     *
+     * <p>In this mode, the user will only be asked to select a service
+     *    if the selected AID has been registered by multiple applications.
      */
-    public static final String PAYMENT_MODE_MANUAL = "manual";
+    public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+    static boolean sIsInitialized = false;
+    static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap();
+    static INfcCardEmulation sService;
 
     final Context mContext;
 
@@ -113,7 +163,7 @@ public final class CardEmulationManager {
                 throw new UnsupportedOperationException();
             }
             try {
-                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HCE)) {
+                if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
                     Log.e(TAG, "This device does not support card emulation");
                     throw new UnsupportedOperationException();
                 }
@@ -137,6 +187,10 @@ public final class CardEmulationManager {
      * Allows an application to query whether a service is currently
      * the default service to handle a card emulation category.
      *
+     * <p>Note that if {@link #getSelectionModeForCategory(String)}
+     * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
+     * return false.
+     *
      * @param service The ComponentName of the service
      * @param category The category
      * @return whether service is currently the default service for the category.
@@ -147,6 +201,10 @@ public final class CardEmulationManager {
         } catch (RemoteException e) {
             // Try one more time
             recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
             try {
                 return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
                         category);
@@ -186,6 +244,33 @@ public final class CardEmulationManager {
     }
 
     /**
+     * Returns the application selection mode for the passed in category.
+     * Valid return values are:
+     * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+     *    application for this category, which will be preferred.
+     * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+     *    every time what app he would like to use in this category.
+     * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+     *    to pick a service if there is a conflict.
+     * @param category The category, for example {@link #CATEGORY_PAYMENT}
+     * @return
+     */
+    public int getSelectionModeForCategory(String category) {
+        if (CATEGORY_PAYMENT.equals(category)) {
+            String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
+                    Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+            if (defaultComponent != null) {
+                return SELECTION_MODE_PREFER_DEFAULT;
+            } else {
+                return SELECTION_MODE_ALWAYS_ASK;
+            }
+        } else {
+            // All other categories are in "only ask if conflict" mode
+            return SELECTION_MODE_ASK_IF_CONFLICT;
+        }
+    }
+
+    /**
      * @hide
      */
     public boolean setDefaultServiceForCategory(ComponentName service, String category) {
index cdc4adb..ae94b2f 100644 (file)
@@ -4,13 +4,11 @@ import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.Service;
 import android.content.Intent;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
-import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.Log;
 
index 24dbf0e..802bedf 100644 (file)
@@ -4285,12 +4285,6 @@ public final class Settings {
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
         /**
-         * Whether to automatically invoke NFC payment app or manually select on tap.
-         * @hide
-         */
-        public static final String NFC_PAYMENT_MODE = "nfc_payment_mode";
-
-        /**
          * Name of a package that the current user has explicitly allowed to see all of that
          * user's notifications.
          *