Merge "HCE API review." into klp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
dd592e2a0d
@@ -15264,12 +15264,12 @@ package android.nfc {
|
||||
|
||||
package android.nfc.cardemulation {
|
||||
|
||||
public final class CardEmulationManager {
|
||||
method public static synchronized android.nfc.cardemulation.CardEmulationManager getInstance(android.nfc.NfcAdapter);
|
||||
public final class CardEmulation {
|
||||
method public static synchronized android.nfc.cardemulation.CardEmulation 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";
|
||||
field public static final java.lang.String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
|
||||
field public static final java.lang.String CATEGORY_OTHER = "other";
|
||||
field public static final java.lang.String CATEGORY_PAYMENT = "payment";
|
||||
field public static final java.lang.String EXTRA_CATEGORY = "category";
|
||||
@@ -15289,15 +15289,15 @@ package android.nfc.cardemulation {
|
||||
method public final void sendResponseApdu(byte[]);
|
||||
field public static final int DEACTIVATION_DESELECTED = 1; // 0x1
|
||||
field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.HostApduService";
|
||||
field public static final java.lang.String SERVICE_META_DATA = "android.nfc.HostApduService";
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
|
||||
field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service";
|
||||
}
|
||||
|
||||
public abstract class OffHostApduService extends android.app.Service {
|
||||
ctor public OffHostApduService();
|
||||
method public abstract android.os.IBinder onBind(android.content.Intent);
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.OffHostApduService";
|
||||
field public static final java.lang.String SERVICE_META_DATA = "android.nfc.OffHostApduService";
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
|
||||
field public static final java.lang.String SERVICE_META_DATA = "android.nfc.cardemulation.off_host_apdu_service";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -105,8 +105,12 @@ public final class ApduServiceInfo implements Parcelable {
|
||||
if (onHost) {
|
||||
parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
|
||||
if (parser == null) {
|
||||
throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
|
||||
" meta-data");
|
||||
Log.d(TAG, "Didn't find service meta-data, trying legacy.");
|
||||
parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA);
|
||||
if (parser == null) {
|
||||
throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
|
||||
" meta-data");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
|
||||
@@ -170,12 +174,12 @@ public final class ApduServiceInfo implements Parcelable {
|
||||
com.android.internal.R.styleable.AidGroup_description);
|
||||
String groupCategory = groupAttrs.getString(
|
||||
com.android.internal.R.styleable.AidGroup_category);
|
||||
if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
|
||||
groupCategory = CardEmulationManager.CATEGORY_OTHER;
|
||||
if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
|
||||
groupCategory = CardEmulation.CATEGORY_OTHER;
|
||||
}
|
||||
currentGroup = mCategoryToGroup.get(groupCategory);
|
||||
if (currentGroup != null) {
|
||||
if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
|
||||
if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
|
||||
Log.e(TAG, "Not allowing multiple aid-groups in the " +
|
||||
groupCategory + " category");
|
||||
currentGroup = null;
|
||||
|
||||
343
core/java/android/nfc/cardemulation/CardEmulation.java
Normal file
343
core/java/android/nfc/cardemulation/CardEmulation.java
Normal file
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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.cardemulation;
|
||||
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
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;
|
||||
import java.util.List;
|
||||
|
||||
public final class CardEmulation {
|
||||
static final String TAG = "CardEmulation";
|
||||
|
||||
/**
|
||||
* Activity action: ask the user to change the default
|
||||
* card emulation service for a certain category. This will
|
||||
* show a dialog that asks the user whether he wants to
|
||||
* replace the current default service with the service
|
||||
* identified with the ComponentName specified in
|
||||
* {@link #EXTRA_SERVICE_COMPONENT}, for the category
|
||||
* specified in {@link #EXTRA_CATEGORY}
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||||
public static final String ACTION_CHANGE_DEFAULT =
|
||||
"android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
|
||||
|
||||
/**
|
||||
* The category extra for {@link #ACTION_CHANGE_DEFAULT}
|
||||
*
|
||||
* @see #ACTION_CHANGE_DEFAULT
|
||||
*/
|
||||
public static final String EXTRA_CATEGORY = "category";
|
||||
|
||||
/**
|
||||
* The ComponentName object passed in as a parcelable
|
||||
* extra for {@link #ACTION_CHANGE_DEFAULT}
|
||||
*
|
||||
* @see #ACTION_CHANGE_DEFAULT
|
||||
*/
|
||||
public static final String EXTRA_SERVICE_COMPONENT = "component";
|
||||
|
||||
/**
|
||||
* The payment category can be used to indicate that an AID
|
||||
* represents a payment application.
|
||||
*/
|
||||
public static final String CATEGORY_PAYMENT = "payment";
|
||||
|
||||
/**
|
||||
* If an AID group does not contain a category, or the
|
||||
* specified category is not defined by the platform version
|
||||
* that is parsing the AID group, all AIDs in the group will
|
||||
* automatically be categorized under the {@link #CATEGORY_OTHER}
|
||||
* category.
|
||||
*/
|
||||
public static final String CATEGORY_OTHER = "other";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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 int SELECTION_MODE_ALWAYS_ASK = 1;
|
||||
|
||||
/**
|
||||
* 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 int SELECTION_MODE_ASK_IF_CONFLICT = 2;
|
||||
|
||||
static boolean sIsInitialized = false;
|
||||
static HashMap<Context, CardEmulation> sCardEmus = new HashMap();
|
||||
static INfcCardEmulation sService;
|
||||
|
||||
final Context mContext;
|
||||
|
||||
private CardEmulation(Context context, INfcCardEmulation service) {
|
||||
mContext = context.getApplicationContext();
|
||||
sService = service;
|
||||
}
|
||||
|
||||
public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
|
||||
if (adapter == null) throw new NullPointerException("NfcAdapter is null");
|
||||
Context context = adapter.getContext();
|
||||
if (context == null) {
|
||||
Log.e(TAG, "NfcAdapter context is null.");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
if (!sIsInitialized) {
|
||||
IPackageManager pm = ActivityThread.getPackageManager();
|
||||
if (pm == null) {
|
||||
Log.e(TAG, "Cannot get PackageManager");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
try {
|
||||
if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
|
||||
Log.e(TAG, "This device does not support card emulation");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "PackageManager query failed.");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
sIsInitialized = true;
|
||||
}
|
||||
CardEmulation manager = sCardEmus.get(context);
|
||||
if (manager == null) {
|
||||
// Get card emu service
|
||||
INfcCardEmulation service = adapter.getCardEmulationService();
|
||||
manager = new CardEmulation(context, service);
|
||||
sCardEmus.put(context, manager);
|
||||
}
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public boolean isDefaultServiceForCategory(ComponentName service, String category) {
|
||||
try {
|
||||
return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
|
||||
} 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);
|
||||
} catch (RemoteException ee) {
|
||||
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Allows an application to query whether a service is currently
|
||||
* the default handler for a specified ISO7816-4 Application ID.
|
||||
*
|
||||
* @param service The ComponentName of the service
|
||||
* @param aid The ISO7816-4 Application ID
|
||||
* @return
|
||||
*/
|
||||
public boolean isDefaultServiceForAid(ComponentName service, String aid) {
|
||||
try {
|
||||
return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
|
||||
} catch (RemoteException e) {
|
||||
// Try one more time
|
||||
recoverService();
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
|
||||
} catch (RemoteException ee) {
|
||||
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
try {
|
||||
return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
|
||||
} catch (RemoteException e) {
|
||||
// Try one more time
|
||||
recoverService();
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
|
||||
category);
|
||||
} catch (RemoteException ee) {
|
||||
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public boolean setDefaultForNextTap(ComponentName service) {
|
||||
try {
|
||||
return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
|
||||
} catch (RemoteException e) {
|
||||
// Try one more time
|
||||
recoverService();
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
|
||||
} catch (RemoteException ee) {
|
||||
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public List<ApduServiceInfo> getServices(String category) {
|
||||
try {
|
||||
return sService.getServices(UserHandle.myUserId(), category);
|
||||
} catch (RemoteException e) {
|
||||
// Try one more time
|
||||
recoverService();
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "Failed to recover CardEmulationService.");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return sService.getServices(UserHandle.myUserId(), category);
|
||||
} catch (RemoteException ee) {
|
||||
Log.e(TAG, "Failed to reach CardEmulationService.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void recoverService() {
|
||||
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
|
||||
sService = adapter.getCardEmulationService();
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,10 @@ import android.util.Log;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TODO Remove when calling .apks are upgraded
|
||||
* @hide
|
||||
*/
|
||||
public final class CardEmulationManager {
|
||||
static final String TAG = "CardEmulationManager";
|
||||
|
||||
|
||||
@@ -40,13 +40,31 @@ public abstract class HostApduService extends Service {
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.SERVICE_ACTION)
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.nfc.HostApduService";
|
||||
"android.nfc.cardemulation.action.HOST_APDU_SERVICE";
|
||||
|
||||
/**
|
||||
* The name of the meta-data element that contains
|
||||
* more information about this service.
|
||||
*/
|
||||
public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
|
||||
public static final String SERVICE_META_DATA =
|
||||
"android.nfc.cardemulation.host_apdu_service";
|
||||
|
||||
/**
|
||||
* The {@link Intent} that must be declared as handled by the service.
|
||||
* TODO Remove
|
||||
* @hide
|
||||
*/
|
||||
public static final String OLD_SERVICE_INTERFACE =
|
||||
"android.nfc.HostApduService";
|
||||
|
||||
/**
|
||||
* The name of the meta-data element that contains
|
||||
* more information about this service.
|
||||
*
|
||||
* TODO Remove
|
||||
* @hide
|
||||
*/
|
||||
public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService";
|
||||
|
||||
/**
|
||||
* Reason for {@link #onDeactivated(int)}.
|
||||
|
||||
@@ -42,13 +42,14 @@ public abstract class OffHostApduService extends Service {
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.SERVICE_ACTION)
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.nfc.OffHostApduService";
|
||||
"android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
|
||||
|
||||
/**
|
||||
* The name of the meta-data element that contains
|
||||
* more information about this service.
|
||||
*/
|
||||
public static final String SERVICE_META_DATA = "android.nfc.OffHostApduService";
|
||||
public static final String SERVICE_META_DATA =
|
||||
"android.nfc.cardemulation.off_host_apdu_service";
|
||||
|
||||
/**
|
||||
* The Android platform itself will not bind to this service,
|
||||
|
||||
Reference in New Issue
Block a user