| /* |
| * 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(); |
| } |
| } |