blob: 124ea1c331c5e12a1340d74929eadc0314906a82 [file] [log] [blame]
/*
* 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;
/**
* TODO Remove when calling .apks are upgraded
* @hide
*/
public final class CardEmulationManager {
static final String TAG = "CardEmulationManager";
/**
* 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_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, CardEmulationManager> sCardEmuManagers = new HashMap();
static INfcCardEmulation sService;
final Context mContext;
private CardEmulationManager(Context context, INfcCardEmulation service) {
mContext = context.getApplicationContext();
sService = service;
}
public static synchronized CardEmulationManager 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;
}
CardEmulationManager manager = sCardEmuManagers.get(context);
if (manager == null) {
// Get card emu service
INfcCardEmulation service = adapter.getCardEmulationService();
manager = new CardEmulationManager(context, service);
sCardEmuManagers.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();
}
}