/* * 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.Manifest; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.UserHandleAware; import android.annotation.UserIdInt; import android.app.Activity; import android.app.role.RoleManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.nfc.Constants; import android.nfc.Flags; import android.nfc.INfcCardEmulation; import android.nfc.NfcAdapter; import android.os.Build; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Log; import java.util.HashMap; import java.util.HexFormat; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; /** * This class can be used to query the state of * NFC card emulation services. * * For a general introduction into NFC card emulation, * please read the * NFC card emulation developer guide.

* *

Use of this class requires the * {@link PackageManager#FEATURE_NFC_HOST_CARD_EMULATION} to be present * on the device. */ public final class CardEmulation { private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); private static final Pattern PLPF_PATTERN = Pattern.compile("[0-9A-Fa-f,\\?,\\*\\.]*"); 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 they want 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}. There is an optional * extra field using {@link Intent#EXTRA_USER} to specify * the {@link UserHandle} of the user that owns the app. * * @deprecated Please use {@link android.app.role.RoleManager#createRequestRoleIntent(String)} * with {@link android.app.role.RoleManager#ROLE_WALLET} parameter * and {@link Activity#startActivityForResult(Intent, int)} instead. */ @Deprecated @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 service {@link ComponentName} object passed in as an * extra for {@link #ACTION_CHANGE_DEFAULT}. * * @see #ACTION_CHANGE_DEFAULT */ public static final String EXTRA_SERVICE_COMPONENT = "component"; /** * Category used for NFC payment services. */ public static final String CATEGORY_PAYMENT = "payment"; /** * Category that can be used for all other card emulation * services. */ public static final String CATEGORY_OTHER = "other"; /** * Return value for {@link #getSelectionModeForCategory(String)}. * *

In this mode, the user has set a default service for this * category. * *

When using ISO-DEP card emulation with {@link HostApduService} * or {@link OffHostApduService}, if a remote NFC device selects * any of the Application IDs (AIDs) * that the default service has registered in this category, * that service will automatically be bound to to handle * the transaction. */ public static final int SELECTION_MODE_PREFER_DEFAULT = 0; /** * Return value for {@link #getSelectionModeForCategory(String)}. * *

In this mode, when using ISO-DEP card emulation with {@link HostApduService} * or {@link OffHostApduService}, whenever an Application ID (AID) of this category * is selected, the user is asked which service they want 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)}. * *

In this mode, when using ISO-DEP card emulation with {@link HostApduService} * or {@link OffHostApduService}, the user will only be asked to select a service * if the Application ID (AID) selected by the reader has been registered by multiple * services. If there is only one service that has registered for the AID, * that service will be invoked directly. */ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; static boolean sIsInitialized = false; static HashMap sCardEmus = new HashMap(); static INfcCardEmulation sService; final Context mContext; private CardEmulation(Context context, INfcCardEmulation service) { mContext = context.getApplicationContext(); sService = service; } /** * Helper to get an instance of this class. * * @param adapter A reference to an NfcAdapter object. * @return */ 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) { PackageManager pm = context.getPackageManager(); if (pm == null) { Log.e(TAG, "Cannot get PackageManager"); throw new UnsupportedOperationException(); } if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { Log.e(TAG, "This device does not support card emulation"); throw new UnsupportedOperationException(); } sIsInitialized = true; } CardEmulation manager = sCardEmus.get(context); if (manager == null) { // Get card emu service INfcCardEmulation service = adapter.getCardEmulationService(); if (service == null) { Log.e(TAG, "This device does not implement the INfcCardEmulation interface."); throw new UnsupportedOperationException(); } 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. * *

Note that if {@link #getSelectionModeForCategory(String)} * returns {@link #SELECTION_MODE_ALWAYS_ASK} or {@link #SELECTION_MODE_ASK_IF_CONFLICT}, * this method will always return false. That is because in these * selection modes a default can't be set at the category level. For categories where * the selection mode is {@link #SELECTION_MODE_ALWAYS_ASK} or * {@link #SELECTION_MODE_ASK_IF_CONFLICT}, use * {@link #isDefaultServiceForAid(ComponentName, String)} to determine whether a service * is the default for a specific AID. * * @param service The ComponentName of the service * @param category The category * @return whether service is currently the default service for the category. * *

Requires the {@link android.Manifest.permission#NFC} permission. */ public boolean isDefaultServiceForCategory(ComponentName service, String category) { try { return sService.isDefaultServiceForCategory(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), 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 whether the service is the default handler for the specified AID * *

Requires the {@link android.Manifest.permission#NFC} permission. */ public boolean isDefaultServiceForAid(ComponentName service, String aid) { try { return sService.isDefaultServiceForAid(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), service, aid); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** *

* Returns whether the user has allowed AIDs registered in the * specified category to be handled by a service that is preferred * by the foreground application, instead of by a pre-configured default. * * Foreground applications can set such preferences using the * {@link #setPreferredService(Activity, ComponentName)} method. *

* Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this method will always * return true. * * @param category The category, e.g. {@link #CATEGORY_PAYMENT} * @return whether AIDs in the category can be handled by a service * specified by the foreground app. */ @SuppressWarnings("NonUserGetterCalled") public boolean categoryAllowsForegroundPreference(String category) { Context contextAsUser = mContext.createContextAsUser( UserHandle.of(UserHandle.myUserId()), 0); RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class); if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) { return true; } if (CATEGORY_PAYMENT.equals(category)) { boolean preferForeground = false; try { preferForeground = Settings.Secure.getInt( contextAsUser.getContentResolver(), Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0; } catch (SettingNotFoundException e) { } return preferForeground; } else { // Allowed for all other categories return true; } } /** * Returns the service selection mode for the passed in category. * Valid return values are: *

{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default * service for this category, which will be preferred. *

{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked * every time what service they would like to use in this category. *

{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked * to pick a service if there is a conflict. * *

* Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default service defined * by the holder of {@link android.app.role.RoleManager#ROLE_WALLET} and is category agnostic. * * @param category The category, for example {@link #CATEGORY_PAYMENT} * @return the selection mode for the passed in category */ public int getSelectionModeForCategory(String category) { if (CATEGORY_PAYMENT.equals(category)) { boolean paymentRegistered = false; try { paymentRegistered = sService.isDefaultPaymentRegistered(); } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return SELECTION_MODE_ALWAYS_ASK; } try { paymentRegistered = sService.isDefaultPaymentRegistered(); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return SELECTION_MODE_ALWAYS_ASK; } } if (paymentRegistered) { return SELECTION_MODE_PREFER_DEFAULT; } else { return SELECTION_MODE_ALWAYS_ASK; } } else { return SELECTION_MODE_ASK_IF_CONFLICT; } } /** * Sets whether when this service becomes the preferred service, if the NFC stack * should enable observe mode or disable observe mode. The default is to not enable observe * mode when a service either the foreground default service or the default payment service so * not calling this method will preserve that behavior. * * @param service The component name of the service * @param enable Whether the service should default to observe mode or not * @return whether the change was successful. */ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) public boolean setShouldDefaultToObserveModeForService(@NonNull ComponentName service, boolean enable) { try { return sService.setShouldDefaultToObserveModeForService( mContext.getUser().getIdentifier(), service, enable); } catch (RemoteException e) { Log.e(TAG, "Failed to reach CardEmulationService."); } return false; } /** * Register a polling loop filter (PLF) for a HostApduService and indicate whether it should * auto-transact or not. The PLF can be sequence of an * even number of at least 2 hexadecimal numbers (0-9, A-F or a-f), representing a series of * bytes. When non-standard polling loop frame matches this sequence exactly, it may be * delivered to {@link HostApduService#processPollingFrames(List)}. If auto-transact * is set to true and this service is currently preferred or there are no other services * registered for this filter then observe mode will also be disabled. * @param service The HostApduService to register the filter for * @param pollingLoopFilter The filter to register * @param autoTransact true to have the NFC stack automatically disable observe mode and allow * transactions to proceed when this filter matches, false otherwise * @return true if the filter was registered, false otherwise * @throws IllegalArgumentException if the passed in string doesn't parse to at least one byte */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean registerPollingLoopFilterForService(@NonNull ComponentName service, @NonNull String pollingLoopFilter, boolean autoTransact) { pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter); try { return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(), service, pollingLoopFilter, autoTransact); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.registerPollingLoopFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopFilter, autoTransact); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Unregister a polling loop filter (PLF) for a HostApduService. If the PLF had previously been * registered via {@link #registerPollingLoopFilterForService(ComponentName, String, boolean)} * for this service it will be removed. * @param service The HostApduService to unregister the filter for * @param pollingLoopFilter The filter to unregister * @return true if the filter was removed, false otherwise * @throws IllegalArgumentException if the passed in string doesn't parse to at least one byte */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean removePollingLoopFilterForService(@NonNull ComponentName service, @NonNull String pollingLoopFilter) { pollingLoopFilter = validatePollingLoopFilter(pollingLoopFilter); try { return sService.removePollingLoopFilterForService(mContext.getUser().getIdentifier(), service, pollingLoopFilter); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.removePollingLoopFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopFilter); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Register a polling loop pattern filter (PLPF) for a HostApduService and indicate whether it * should auto-transact or not. The pattern may include the characters 0-9 and A-F as well as * the regular expression operators `.`, `?` and `*`. When the beginning of anon-standard * polling loop frame matches this sequence exactly, it may be delivered to * {@link HostApduService#processPollingFrames(List)}. If auto-transact is set to true and this * service is currently preferred or there are no other services registered for this filter * then observe mode will also be disabled. * @param service The HostApduService to register the filter for * @param pollingLoopPatternFilter The pattern filter to register, must to be compatible with * {@link java.util.regex.Pattern#compile(String)} and only contain hexadecimal numbers * and `.`, `?` and `*` operators * @param autoTransact true to have the NFC stack automatically disable observe mode and allow * transactions to proceed when this filter matches, false otherwise * @return true if the filter was registered, false otherwise * @throws IllegalArgumentException if the filter containst elements other than hexadecimal * numbers and `.`, `?` and `*` operators * @throws java.util.regex.PatternSyntaxException if the regex syntax is invalid */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean registerPollingLoopPatternFilterForService(@NonNull ComponentName service, @NonNull String pollingLoopPatternFilter, boolean autoTransact) { pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter); try { return sService.registerPollingLoopPatternFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter, autoTransact); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.registerPollingLoopPatternFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter, autoTransact); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Unregister a polling loop pattern filter (PLPF) for a HostApduService. If the PLF had * previously been registered via * {@link #registerPollingLoopFilterForService(ComponentName, String, boolean)} for this * service it will be removed. * @param service The HostApduService to unregister the filter for * @param pollingLoopPatternFilter The filter to unregister, must to be compatible with * {@link java.util.regex.Pattern#compile(String)} and only contain hexadecimal numbers * and`.`, `?` and `*` operators * @return true if the filter was removed, false otherwise * @throws IllegalArgumentException if the filter containst elements other than hexadecimal * numbers and `.`, `?` and `*` operators * @throws java.util.regex.PatternSyntaxException if the regex syntax is invalid */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean removePollingLoopPatternFilterForService(@NonNull ComponentName service, @NonNull String pollingLoopPatternFilter) { pollingLoopPatternFilter = validatePollingLoopPatternFilter(pollingLoopPatternFilter); try { return sService.removePollingLoopPatternFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.removePollingLoopPatternFilterForService( mContext.getUser().getIdentifier(), service, pollingLoopPatternFilter); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Registers a list of AIDs for a specific category for the * specified service. * *

If a list of AIDs for that category was previously * registered for this service (either statically * through the manifest, or dynamically by using this API), * that list of AIDs will be replaced with this one. * *

Note that you can only register AIDs for a service that * is running under the same UID as the caller of this API. Typically * this means you need to call this from the same * package as the service itself, though UIDs can also * be shared between packages using shared UIDs. * * @param service The component name of the service * @param category The category of AIDs to be registered * @param aids A list containing the AIDs to be registered * @return whether the registration was successful. */ public boolean registerAidsForService(ComponentName service, String category, List aids) { AidGroup aidGroup = new AidGroup(aids, category); try { return sService.registerAidGroupForService(mContext.getUser().getIdentifier(), service, aidGroup); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.registerAidGroupForService(mContext.getUser().getIdentifier(), service, aidGroup); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Unsets the off-host Secure Element for the given service. * *

Note that this will only remove Secure Element that was dynamically * set using the {@link #setOffHostForService(ComponentName, String)} * and resets it to a value that was statically assigned using manifest. * *

Note that you can only unset off-host SE for a service that * is running under the same UID as the caller of this API. Typically * this means you need to call this from the same * package as the service itself, though UIDs can also * be shared between packages using shared UIDs. * * @param service The component name of the service * @return whether the registration was successful. */ @RequiresPermission(android.Manifest.permission.NFC) @NonNull public boolean unsetOffHostForService(@NonNull ComponentName service) { NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); if (adapter == null) { return false; } try { return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.unsetOffHostForService(mContext.getUser().getIdentifier(), service); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Sets the off-host Secure Element for the given service. * *

If off-host SE was initially set (either statically * through the manifest, or dynamically by using this API), * it will be replaced with this one. All AIDs registered by * this service will be re-routed to this Secure Element if * successful. AIDs that was statically assigned using manifest * will re-route to off-host SE that stated in manifest after NFC * toggle. * *

Note that you can only set off-host SE for a service that * is running under the same UID as the caller of this API. Typically * this means you need to call this from the same * package as the service itself, though UIDs can also * be shared between packages using shared UIDs. * *

Registeration will be successful only if the Secure Element * exists on the device. * * @param service The component name of the service * @param offHostSecureElement Secure Element to register the AID to. Only accept strings with * prefix SIM or prefix eSE. * Ref: GSMA TS.26 - NFC Handset Requirements * TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be * SIM[smartcard slot] * (e.g. SIM/SIM1, SIM2… SIMn). * TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be * eSE[number] * (e.g. eSE/eSE1, eSE2, etc.). * @return whether the registration was successful. */ @RequiresPermission(android.Manifest.permission.NFC) @NonNull public boolean setOffHostForService(@NonNull ComponentName service, @NonNull String offHostSecureElement) { boolean validSecureElement = false; NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); if (adapter == null || offHostSecureElement == null) { return false; } List validSE = adapter.getSupportedOffHostSecureElements(); if ((offHostSecureElement.startsWith("eSE") && !validSE.contains("eSE")) || (offHostSecureElement.startsWith("SIM") && !validSE.contains("SIM"))) { return false; } if (!offHostSecureElement.startsWith("eSE") && !offHostSecureElement.startsWith("SIM")) { return false; } if (offHostSecureElement.equals("eSE")) { offHostSecureElement = "eSE1"; } else if (offHostSecureElement.equals("SIM")) { offHostSecureElement = "SIM1"; } try { return sService.setOffHostForService(mContext.getUser().getIdentifier(), service, offHostSecureElement); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.setOffHostForService(mContext.getUser().getIdentifier(), service, offHostSecureElement); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Retrieves the currently registered AIDs for the specified * category for a service. * *

Note that this will only return AIDs that were dynamically * registered using {@link #registerAidsForService(ComponentName, String, List)} * method. It will *not* return AIDs that were statically registered * in the manifest. * * @param service The component name of the service * @param category The category for which the AIDs were registered, * e.g. {@link #CATEGORY_PAYMENT} * @return The list of AIDs registered for this category, or null if it couldn't be found. */ public List getAidsForService(ComponentName service, String category) { try { AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(), service, category); return (group != null ? group.getAids() : null); } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return null; } try { AidGroup group = sService.getAidGroupForService(mContext.getUser().getIdentifier(), service, category); return (group != null ? group.getAids() : null); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); return null; } } } /** * Removes a previously registered list of AIDs for the specified category for the * service provided. * *

Note that this will only remove AIDs that were dynamically * registered using the {@link #registerAidsForService(ComponentName, String, List)} * method. It will *not* remove AIDs that were statically registered in * the manifest. If dynamically registered AIDs are removed using * this method, and a statically registered AID group for the same category * exists in the manifest, the static AID group will become active again. * * @param service The component name of the service * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT} * @return whether the group was successfully removed. */ public boolean removeAidsForService(ComponentName service, String category) { try { return sService.removeAidGroupForService(mContext.getUser().getIdentifier(), 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.removeAidGroupForService(mContext.getUser().getIdentifier(), service, category); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Allows a foreground application to specify which card emulation service * should be preferred while a specific Activity is in the foreground. * *

The specified Activity must currently be in resumed state. A good * paradigm is to call this method in your {@link Activity#onResume}, and to call * {@link #unsetPreferredService(Activity)} in your {@link Activity#onPause}. * *

This method call will fail in two specific scenarios: *

* *

Use {@link #categoryAllowsForegroundPreference(String)} to determine * whether foreground apps can override the default payment service. * *

Note that this preference is not persisted by the OS, and hence must be * called every time the Activity is resumed. * * @param activity The activity which prefers this service to be invoked * @param service The service to be preferred while this activity is in the foreground * @return whether the registration was successful */ public boolean setPreferredService(Activity activity, ComponentName service) { // Verify the activity is in the foreground before calling into NfcService if (activity == null || service == null) { throw new NullPointerException("activity or service or category is null"); } try { return sService.setPreferredService(service); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.setPreferredService(service); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Unsets the preferred service for the specified Activity. * *

Note that the specified Activity must still be in resumed * state at the time of this call. A good place to call this method * is in your {@link Activity#onPause} implementation. * * @param activity The activity which the service was registered for * @return true when successful */ public boolean unsetPreferredService(Activity activity) { if (activity == null) { throw new NullPointerException("activity is null"); } try { return sService.unsetPreferredService(); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.unsetPreferredService(); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Some devices may allow an application to register all * AIDs that starts with a certain prefix, e.g. * "A000000004*" to register all MasterCard AIDs. * * Use this method to determine whether this device * supports registering AID prefixes. * * @return whether AID prefix registering is supported on this device. */ public boolean supportsAidPrefixRegistration() { try { return sService.supportsAidPrefixRegistration(); } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.supportsAidPrefixRegistration(); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Retrieves the registered AIDs for the preferred payment service. * * @return The list of AIDs registered for this category, or null if it couldn't be found. */ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) @Nullable public List getAidsForPreferredPaymentService() { try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService( mContext.getUser().getIdentifier()); return (serviceInfo != null ? serviceInfo.getAids() : null); } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUser().getIdentifier()); return (serviceInfo != null ? serviceInfo.getAids() : null); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } } } /** * Retrieves the route destination for the preferred payment service. * *

* Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This * will return the route for one of the services registered by the role holder (if any). If * there are multiple services registered, it is unspecified which of those will be used to * determine the route. * * @return The route destination secure element name of the preferred payment service. * HCE payment: "Host" * OffHost payment: 1. String with prefix SIM or prefix eSE string. * Ref: GSMA TS.26 - NFC Handset Requirements * TS26_NFC_REQ_069: For UICC, Secure Element Name SHALL be * SIM[smartcard slot] * (e.g. SIM/SIM1, SIM2… SIMn). * TS26_NFC_REQ_070: For embedded SE, Secure Element Name SHALL be * eSE[number] * (e.g. eSE/eSE1, eSE2, etc.). * 2. "OffHost" if the payment service does not specify secure element * name. */ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) @Nullable public String getRouteDestinationForPreferredPaymentService() { try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService( mContext.getUser().getIdentifier()); if (serviceInfo != null) { if (!serviceInfo.isOnHost()) { return serviceInfo.getOffHostSecureElement() == null ? "OffHost" : serviceInfo.getOffHostSecureElement(); } return "Host"; } return null; } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUser().getIdentifier()); if (serviceInfo != null) { if (!serviceInfo.isOnHost()) { return serviceInfo.getOffHostSecureElement() == null ? "Offhost" : serviceInfo.getOffHostSecureElement(); } return "Host"; } return null; } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } } } /** * Returns a user-visible description of the preferred payment service. * *

* Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the preferred payment service * no longer exists and is replaced by {@link android.app.role.RoleManager#ROLE_WALLET}. This * will return the description for one of the services registered by the role holder (if any). * If there are multiple services registered, it is unspecified which of those will be used * to obtain the service description here. * * @return the preferred payment service description */ @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) @Nullable public CharSequence getDescriptionForPreferredPaymentService() { try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService( mContext.getUser().getIdentifier()); return (serviceInfo != null ? serviceInfo.getDescription() : null); } catch (RemoteException e) { recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } try { ApduServiceInfo serviceInfo = sService.getPreferredPaymentService(mContext.getUser().getIdentifier()); return (serviceInfo != null ? serviceInfo.getDescription() : null); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover CardEmulationService."); throw e.rethrowFromSystemServer(); } } } /** * @hide */ public boolean setDefaultServiceForCategory(ComponentName service, String category) { try { return sService.setDefaultServiceForCategory(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), service, category); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * @hide */ public boolean setDefaultForNextTap(ComponentName service) { try { return sService.setDefaultForNextTap(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), service); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * @hide */ public boolean setDefaultForNextTap(int userId, ComponentName service) { try { return sService.setDefaultForNextTap(userId, 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(userId, service); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * @hide */ public List getServices(String category) { try { return sService.getServices(mContext.getUser().getIdentifier(), 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(mContext.getUser().getIdentifier(), category); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return null; } } } /** * Retrieves list of services registered of the provided category for the provided user. * * @param category Category string, one of {@link #CATEGORY_PAYMENT} or {@link #CATEGORY_OTHER} * @param userId the user handle of the user whose information is being requested. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @NonNull public List getServices(@NonNull String category, @UserIdInt int userId) { try { return sService.getServices(userId, 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(userId, category); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return null; } } } /** * Tests the validity of the polling loop filter. * @param pollingLoopFilter The polling loop filter to test. * * @hide */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public static @NonNull String validatePollingLoopFilter(@NonNull String pollingLoopFilter) { // Verify hex characters byte[] plfBytes = HexFormat.of().parseHex(pollingLoopFilter); if (plfBytes.length == 0) { throw new IllegalArgumentException( "Polling loop filter must contain at least one byte."); } return HexFormat.of().withUpperCase().formatHex(plfBytes); } /** * Tests the validity of the polling loop pattern filter. * @param pollingLoopPatternFilter The polling loop filter to test. * * @hide */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public static @NonNull String validatePollingLoopPatternFilter( @NonNull String pollingLoopPatternFilter) { // Verify hex characters if (!PLPF_PATTERN.matcher(pollingLoopPatternFilter).matches()) { throw new IllegalArgumentException( "Polling loop pattern filters may only contain hexadecimal numbers, ?s and *s"); } return Pattern.compile(pollingLoopPatternFilter.toUpperCase(Locale.ROOT)).toString(); } /** * A valid AID according to ISO/IEC 7816-4: *

* * @hide */ public static boolean isValidAid(String aid) { if (aid == null) return false; // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*') if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) { Log.e(TAG, "AID " + aid + " is not a valid AID."); return false; } // If not a prefix/subset AID, the total length must be even (even # of AID chars) if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) { Log.e(TAG, "AID " + aid + " is not a valid AID."); return false; } // Verify hex characters if (!AID_PATTERN.matcher(aid).matches()) { Log.e(TAG, "AID " + aid + " is not a valid AID."); return false; } return true; } /** * Allows to set or unset preferred service (category other) to avoid AID Collision. * * @param service The ComponentName of the service * @param status true to enable, false to disable * @param userId the user handle of the user whose information is being requested. * @return set service for the category and true if service is already set return false. * * @hide */ public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status, int userId) { if (service == null) { throw new NullPointerException("activity or service or category is null"); } try { return sService.setServiceEnabledForCategoryOther(userId, service, status); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.setServiceEnabledForCategoryOther(userId, service, status); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Setting NFC controller routing table, which includes Protocol Route and Technology Route, * while this Activity is in the foreground. * * The parameter set to null can be used to keep current values for that entry. *

* Example usage in an Activity that requires to set proto route to "ESE" and keep tech route: *

      * protected void onResume() {
      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
      * }
*

* Also activities must call this method when it goes to the background, * with all parameters set to null. * @param activity The Activity that requests NFC controller routing table to be changed. * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE". * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE". * @return true if operation is successful and false otherwise * * This is a high risk API and only included to support mainline effort * @hide */ public boolean overrideRoutingTable(Activity activity, String protocol, String technology) { if (activity == null) { throw new NullPointerException("activity or service or category is null"); } if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } try { return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } /** * Restore the NFC controller routing table, * which was changed by {@link #overrideRoutingTable(Activity, String, String)} * * @param activity The Activity that requested NFC controller routing table to be changed. * @return true if operation is successful and false otherwise * * @hide */ public boolean recoverRoutingTable(Activity activity) { if (activity == null) { throw new NullPointerException("activity is null"); } if (!activity.isResumed()) { throw new IllegalArgumentException("Activity must be resumed."); } try { return sService.recoverRoutingTable(UserHandle.myUserId()); } catch (RemoteException e) { // Try one more time recoverService(); if (sService == null) { Log.e(TAG, "Failed to recover CardEmulationService."); return false; } try { return sService.recoverRoutingTable(UserHandle.myUserId()); } catch (RemoteException ee) { Log.e(TAG, "Failed to reach CardEmulationService."); return false; } } } void recoverService() { NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); sService = adapter.getCardEmulationService(); } /** * Returns the value of {@link Settings.Secure#NFC_PAYMENT_DEFAULT_COMPONENT}. * * @param context A context * @return A ComponentName for the setting value, or null. * * @hide */ @SystemApi @UserHandleAware @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) @SuppressWarnings("AndroidFrameworkClientSidePermissionCheck") @FlaggedApi(android.permission.flags.Flags.FLAG_WALLET_ROLE_ENABLED) @Nullable public static ComponentName getPreferredPaymentService(@NonNull Context context) { context.checkCallingOrSelfPermission(Manifest.permission.NFC_PREFERRED_PAYMENT_INFO); String defaultPaymentComponent = Settings.Secure.getString(context.getContentResolver(), Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT); if (defaultPaymentComponent == null) { return null; } return ComponentName.unflattenFromString(defaultPaymentComponent); } }