/* * Copyright (C) 2024 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; import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_DH; import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE; import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_NDEF_NFCEE; import static android.nfc.cardemulation.CardEmulation.PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC; import static android.nfc.cardemulation.CardEmulation.routeIntToString; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.DurationMillisLong; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.CardEmulation; import android.nfc.cardemulation.CardEmulation.ProtocolAndTechnologyRoute; import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.se.omapi.Reader; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; /** * Used for OEM extension APIs. * This class holds all the APIs and callbacks defined for OEMs/vendors to extend the NFC stack * for their proprietary features. * * @hide */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @SystemApi public final class NfcOemExtension { private static final String TAG = "NfcOemExtension"; private static final int OEM_EXTENSION_RESPONSE_THRESHOLD_MS = 2000; private static final int TYPE_TECHNOLOGY = 0; private static final int TYPE_PROTOCOL = 1; private static final int TYPE_AID = 2; private static final int TYPE_SYSTEMCODE = 3; private final NfcAdapter mAdapter; private final NfcOemExtensionCallback mOemNfcExtensionCallback; private boolean mIsRegistered = false; private final Map mCallbackMap = new HashMap<>(); private final Context mContext; private final Object mLock = new Object(); private boolean mCardEmulationActivated = false; private boolean mRfFieldActivated = false; private boolean mRfDiscoveryStarted = false; private boolean mEeListenActivated = false; /** * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled. *

OEM extension modules should use this intent to start their extension service

* @hide */ public static final String ACTION_OEM_EXTENSION_INIT = "android.nfc.action.OEM_EXTENSION_INIT"; /** * Mode Type for {@link #setControllerAlwaysOnMode(int)}. * Enables the controller in default mode when NFC is disabled (existing API behavior). * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int ENABLE_DEFAULT = NfcAdapter.CONTROLLER_ALWAYS_ON_MODE_DEFAULT; /** * Mode Type for {@link #setControllerAlwaysOnMode(int)}. * Enables the controller in transparent mode when NFC is disabled. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int ENABLE_TRANSPARENT = 2; /** * Mode Type for {@link #setControllerAlwaysOnMode(int)}. * Enables the controller and initializes and enables the EE subsystem when NFC is disabled. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int ENABLE_EE = 3; /** * Mode Type for {@link #setControllerAlwaysOnMode(int)}. * Disable the Controller Always On Mode. * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}. * @hide */ @SystemApi @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int DISABLE = NfcAdapter.CONTROLLER_ALWAYS_ON_DISABLE; /** * Possible controller modes for {@link #setControllerAlwaysOnMode(int)}. * * @hide */ @IntDef(prefix = { "" }, value = { ENABLE_DEFAULT, ENABLE_TRANSPARENT, ENABLE_EE, DISABLE, }) @Retention(RetentionPolicy.SOURCE) public @interface ControllerMode{} /** * Technology Type for {@link #getActiveNfceeList()}. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int NFCEE_TECH_NONE = 0; /** * Technology Type for {@link #getActiveNfceeList()}. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int NFCEE_TECH_A = 1; /** * Technology Type for {@link #getActiveNfceeList()}. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int NFCEE_TECH_B = 1 << 1; /** * Technology Type for {@link #getActiveNfceeList()}. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public static final int NFCEE_TECH_F = 1 << 2; /** * Nfc technology flags for {@link #getActiveNfceeList()}. * * @hide */ @IntDef(flag = true, value = { NFCEE_TECH_NONE, NFCEE_TECH_A, NFCEE_TECH_B, NFCEE_TECH_F, }) @Retention(RetentionPolicy.SOURCE) public @interface NfceeTechnology {} /** * Event that Host Card Emulation is activated. */ public static final int HCE_ACTIVATE = 1; /** * Event that some data is transferred in Host Card Emulation. */ public static final int HCE_DATA_TRANSFERRED = 2; /** * Event that Host Card Emulation is deactivated. */ public static final int HCE_DEACTIVATE = 3; /** * Possible events from {@link Callback#onHceEventReceived}. * * @hide */ @IntDef(value = { HCE_ACTIVATE, HCE_DATA_TRANSFERRED, HCE_DEACTIVATE }) @Retention(RetentionPolicy.SOURCE) public @interface HostCardEmulationAction {} /** * Status code returned when the polling state change request succeeded. * @see #pausePolling() * @see #resumePolling() */ public static final int POLLING_STATE_CHANGE_SUCCEEDED = 1; /** * Status code returned when the polling state change request is already in * required state. * @see #pausePolling() * @see #resumePolling() */ public static final int POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE = 2; /** * Possible status codes for {@link #pausePolling()} and * {@link #resumePolling()}. * @hide */ @IntDef(value = { POLLING_STATE_CHANGE_SUCCEEDED, POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE, }) @Retention(RetentionPolicy.SOURCE) public @interface PollingStateChangeStatusCode {} /** * Status OK */ public static final int STATUS_OK = 0; /** * Status unknown error */ public static final int STATUS_UNKNOWN_ERROR = 1; /** * Status codes passed to OEM extension callbacks. * * @hide */ @IntDef(value = { STATUS_OK, STATUS_UNKNOWN_ERROR }) @Retention(RetentionPolicy.SOURCE) public @interface StatusCode {} /** * Routing commit succeeded. */ public static final int COMMIT_ROUTING_STATUS_OK = 0; /** * Routing commit failed. */ public static final int COMMIT_ROUTING_STATUS_FAILED = 3; /** * Routing commit failed due to the update is in progress. */ public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; /** * Status codes returned when calling {@link #forceRoutingTableCommit()} * @hide */ @IntDef(prefix = "COMMIT_ROUTING_STATUS_", value = { COMMIT_ROUTING_STATUS_OK, COMMIT_ROUTING_STATUS_FAILED, COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS, }) @Retention(RetentionPolicy.SOURCE) public @interface CommitRoutingStatusCode {} /** * Interface for Oem extensions for NFC. */ public interface Callback { /** * Notify Oem to tag is connected or not * ex - if tag is connected notify cover and Nfctest app if app is in testing mode * * @param connected status of the tag true if tag is connected otherwise false */ void onTagConnected(boolean connected); /** * Update the Nfc Adapter State * @param state new state that need to be updated */ void onStateUpdated(@NfcAdapter.AdapterState int state); /** * Check if NfcService apply routing method need to be skipped for * some feature. * @param isSkipped The {@link Consumer} to be completed. If apply routing can be skipped, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. */ void onApplyRouting(@NonNull Consumer isSkipped); /** * Check if NfcService ndefRead method need to be skipped To skip * and start checking for presence of tag * @param isSkipped The {@link Consumer} to be completed. If Ndef read can be skipped, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. */ void onNdefRead(@NonNull Consumer isSkipped); /** * Method to check if Nfc is allowed to be enabled by OEMs. * @param isAllowed The {@link Consumer} to be completed. If enabling NFC is allowed, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. * false if NFC cannot be enabled at this time. */ void onEnableRequested(@NonNull Consumer isAllowed); /** * Method to check if Nfc is allowed to be disabled by OEMs. * @param isAllowed The {@link Consumer} to be completed. If disabling NFC is allowed, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. * false if NFC cannot be disabled at this time. */ void onDisableRequested(@NonNull Consumer isAllowed); /** * Callback to indicate that Nfc starts to boot. */ void onBootStarted(); /** * Callback to indicate that Nfc starts to enable. */ void onEnableStarted(); /** * Callback to indicate that Nfc starts to disable. */ void onDisableStarted(); /** * Callback to indicate if NFC boots successfully or not. * @param status the status code indicating if boot finished successfully */ void onBootFinished(@StatusCode int status); /** * Callback to indicate if NFC is successfully enabled. * @param status the status code indicating if enable finished successfully */ void onEnableFinished(@StatusCode int status); /** * Callback to indicate if NFC is successfully disabled. * @param status the status code indicating if disable finished successfully */ void onDisableFinished(@StatusCode int status); /** * Check if NfcService tag dispatch need to be skipped. * @param isSkipped The {@link Consumer} to be completed. If tag dispatch can be skipped, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. */ void onTagDispatch(@NonNull Consumer isSkipped); /** * Notifies routing configuration is changed. * @param isCommitRoutingSkipped The {@link Consumer} to be * completed. If routing commit should be skipped, * the {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. */ void onRoutingChanged(@NonNull Consumer isCommitRoutingSkipped); /** * API to activate start stop cpu boost on hce event. * *

When HCE is activated, transferring data, and deactivated, * must call this method to activate, start and stop cpu boost respectively. * @param action Flag indicating actions to activate, start and stop cpu boost. */ void onHceEventReceived(@HostCardEmulationAction int action); /** * API to notify when reader option has been changed using * {@link NfcAdapter#enableReaderOption(boolean)} by some app. * @param enabled Flag indicating ReaderMode enabled/disabled */ void onReaderOptionChanged(boolean enabled); /** * Notifies NFC is activated in listen mode. * NFC Forum NCI-2.3 ch.5.2.6 specification * *

NFCC is ready to communicate with a Card reader * * @param isActivated true, if card emulation activated, else de-activated. */ void onCardEmulationActivated(boolean isActivated); /** * Notifies the Remote NFC Endpoint RF Field is detected. * NFC Forum NCI-2.3 ch.5.3 specification * * @param isActive true, if RF Field is ON, else RF Field is OFF. */ void onRfFieldDetected(boolean isActive); /** * Notifies the NFC RF discovery is started or in the IDLE state. * NFC Forum NCI-2.3 ch.5.2 specification * * @param isDiscoveryStarted true, if RF discovery started, else RF state is Idle. */ void onRfDiscoveryStarted(boolean isDiscoveryStarted); /** * Notifies the NFCEE (NFC Execution Environment) Listen has been activated. * * @param isActivated true, if EE Listen is ON, else EE Listen is OFF. */ void onEeListenActivated(boolean isActivated); /** * Notifies that some NFCEE (NFC Execution Environment) has been updated. * *

This indicates that some applet has been installed/updated/removed in * one of the NFCEE's. *

*/ void onEeUpdated(); /** * Gets the intent to find the OEM package in the OEM App market. If the consumer returns * {@code null} or a timeout occurs, the intent from the first available package will be * used instead. * * @param packages the OEM packages name stored in the tag * @param intentConsumer The {@link Consumer} to be completed. * The {@link Consumer#accept(Object)} should be called with * the Intent required. * */ void onGetOemAppSearchIntent(@NonNull List packages, @NonNull Consumer intentConsumer); /** * Checks if the NDEF message contains any specific OEM package executable content * * @param tag the {@link android.nfc.Tag Tag} * @param message NDEF Message to read from tag * @param hasOemExecutableContent The {@link Consumer} to be completed. If there is * OEM package executable content, the * {@link Consumer#accept(Object)} should be called with * {@link Boolean#TRUE}, otherwise call with * {@link Boolean#FALSE}. */ void onNdefMessage(@NonNull Tag tag, @NonNull NdefMessage message, @NonNull Consumer hasOemExecutableContent); /** * Callback to indicate the app chooser activity should be launched for handling CE * transaction. This is invoked for example when there are more than 1 app installed that * can handle the HCE transaction. OEMs can launch the Activity based * on their requirement. * * @param selectedAid the selected AID from APDU * @param services {@link ApduServiceInfo} of the service triggering the activity * @param failedComponent the component failed to be resolved * @param category the category of the service */ void onLaunchHceAppChooserActivity(@NonNull String selectedAid, @NonNull List services, @NonNull ComponentName failedComponent, @NonNull String category); /** * Callback to indicate tap again dialog should be launched for handling HCE transaction. * This is invoked for example when a CE service needs the device to unlocked before * handling the transaction. OEMs can launch the Activity based on their requirement. * * @param service {@link ApduServiceInfo} of the service triggering the dialog * @param category the category of the service */ void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category); /** * Callback to indicate that routing table is full and the OEM can optionally launch a * dialog to request the user to remove some Card Emulation apps from the device to free * routing table space. */ void onRoutingTableFull(); /** * Callback when OEM specified log event are notified. * @param item the log items that contains log information of NFC event. */ void onLogEventNotified(@NonNull OemLogItems item); /** * Callback to to extract OEM defined packages from given NDEF message when * a NFC tag is detected. These are used to handle NFC tags encoded with a * proprietary format for storing app name (Android native app format). * * @param message NDEF message containing OEM package names * @param packageConsumer The {@link Consumer} to be completed. * The {@link Consumer#accept(Object)} should be called with * the list of package names. */ void onExtractOemPackages(@NonNull NdefMessage message, @NonNull Consumer> packageConsumer); } /** * Constructor to be used only by {@link NfcAdapter}. */ NfcOemExtension(@NonNull Context context, @NonNull NfcAdapter adapter) { mContext = context; mAdapter = adapter; mOemNfcExtensionCallback = new NfcOemExtensionCallback(); } /** @hide */ @VisibleForTesting public NfcOemExtensionCallback getOemNfcExtensionCallback() { return mOemNfcExtensionCallback; } /** * Get an instance of {@link T4tNdefNfcee} object for performing T4T (Type-4 Tag) * NDEF (NFC Data Exchange Format) NFCEE (NFC Execution Environment) operations. * This can be used to write NDEF data to emulate a T4T tag in an NFCEE * (NFC Execution Environment - eSE, SIM, etc). Refer to the NFC forum specification * "NFCForum-TS-NCI-2.3 section 10.4" and "NFCForum-TS-T4T-1.1 section 4.2" for more details. * * This is a singleton object which shall be used by OEM extension module to do NDEF-NFCEE * read/write operations. * *

Returns {@link T4tNdefNfcee} *

Does not cause any RF activity and does not block. * @return NFC Data Exchange Format (NDEF) NFC Execution Environment (NFCEE) object * @hide */ @SystemApi @NonNull @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public T4tNdefNfcee getT4tNdefNfcee() { return T4tNdefNfcee.getInstance(); } /** * Register an {@link Callback} to listen for NFC oem extension callbacks * Multiple clients can register and callbacks will be invoked asynchronously. * *

The provided callback will be invoked by the given {@link Executor}. * As part of {@link #registerCallback(Executor, Callback)} the * {@link Callback} will be invoked with current NFC state * before the {@link #registerCallback(Executor, Callback)} function completes. * * @param executor an {@link Executor} to execute given callback * @param callback oem implementation of {@link Callback} */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) { synchronized (mLock) { if (executor == null || callback == null) { Log.e(TAG, "Executor and Callback must not be null!"); throw new IllegalArgumentException(); } if (mCallbackMap.containsKey(callback)) { Log.e(TAG, "Callback already registered. Unregister existing callback before" + "registering"); throw new IllegalArgumentException(); } mCallbackMap.put(callback, executor); if (!mIsRegistered) { NfcAdapter.callService(() -> { NfcAdapter.sService.registerOemExtensionCallback(mOemNfcExtensionCallback); mIsRegistered = true; }); } else { updateNfCState(callback, executor); } } } private void updateNfCState(Callback callback, Executor executor) { if (callback != null) { Log.i(TAG, "updateNfCState"); executor.execute(() -> { callback.onCardEmulationActivated(mCardEmulationActivated); callback.onRfFieldDetected(mRfFieldActivated); callback.onRfDiscoveryStarted(mRfDiscoveryStarted); callback.onEeListenActivated(mEeListenActivated); }); } } /** * Unregister the specified {@link Callback} * *

The same {@link Callback} object used when calling * {@link #registerCallback(Executor, Callback)} must be used. * *

Callbacks are automatically unregistered when an application process goes away * * @param callback oem implementation of {@link Callback} */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull Callback callback) { synchronized (mLock) { if (!mCallbackMap.containsKey(callback) || !mIsRegistered) { Log.e(TAG, "Callback not registered"); throw new IllegalArgumentException(); } if (mCallbackMap.size() == 1) { NfcAdapter.callService(() -> { NfcAdapter.sService.unregisterOemExtensionCallback(mOemNfcExtensionCallback); mIsRegistered = false; mCallbackMap.remove(callback); }); } else { mCallbackMap.remove(callback); } } } /** * Clear NfcService preference, interface method to clear NFC preference values on OEM specific * events. For ex: on soft reset, Nfc default values needs to be overridden by OEM defaults. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference() { NfcAdapter.callService(() -> NfcAdapter.sService.clearPreference()); } /** * Get the screen state from system and set it to current screen state. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState() { NfcAdapter.callService(() -> NfcAdapter.sService.setScreenState()); } /** * Check if the firmware needs updating. * *

If an update is needed, a firmware will be triggered when NFC is disabled. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate() { NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware()); } /** * Get the Active NFCEE (NFC Execution Environment) List * * @return Map< String, @NfceeTechnology Integer > * A HashMap where keys are activated secure elements and * the values are bitmap of technologies supported by each secure element: * NFCEE_TECH_A == 0x1 * NFCEE_TECH_B == 0x2 * NFCEE_TECH_F == 0x4 * and keys can contain "eSE" and "SIM" with a number, * in case of failure an empty map is returned. * @see Reader#getName() for the list of possible NFCEE names. */ @NonNull @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public Map getActiveNfceeList() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.fetchActiveNfceeList(), new HashMap()); } /** * Sets NFC controller always on feature. *

This API is for the NFCC internal state management. It allows to discriminate * the controller function from the NFC function by keeping the NFC controller on without * any NFC RF enabled if necessary. *

This call is asynchronous, register listener {@link NfcAdapter.ControllerAlwaysOnListener} * by {@link NfcAdapter#registerControllerAlwaysOnListener} to find out when the operation is * complete. *

Note: This adds more always on modes on top of existing * {@link NfcAdapter#setControllerAlwaysOn(boolean)} API which can be used to set the NFCC in * only {@link #ENABLE_DEFAULT} and {@link #DISABLE} modes. * @param mode one of {@link ControllerMode} modes * @throws UnsupportedOperationException if *

  • if FEATURE_NFC, FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF, * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE * are unavailable
  • *
  • if the feature is unavailable @see NfcAdapter#isNfcControllerAlwaysOnSupported()
  • * @hide * @see NfcAdapter#setControllerAlwaysOn(boolean) */ @SystemApi @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(@ControllerMode int mode) { if (!NfcAdapter.sHasNfcFeature && !NfcAdapter.sHasCeFeature) { throw new UnsupportedOperationException(); } NfcAdapter.callService(() -> NfcAdapter.sService.setControllerAlwaysOn(mode)); } /** * Triggers NFC initialization. If OEM extension is registered * (indicated via `enable_oem_extension` NFC overlay), the NFC stack initialization at bootup * is delayed until the OEM extension app triggers the initialization via this call. */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization() { NfcAdapter.callService(() -> NfcAdapter.sService.triggerInitialization()); } /** * Gets the last user toggle status. * @return true if NFC is set to ON, false otherwise */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.getSettingStatus(), false); } /** * Checks if the tag is present or not. * @return true if the tag is present, false otherwise */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.isTagPresent(), false); } /** * Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond. * In case of {@code timeoutInMs} is zero, polling will be stopped indefinitely. * Use {@link #resumePolling()} to resume the polling. * Use {@link #getMaxPausePollingTimeoutMs()} to check the max timeout value. * @param timeoutInMs the pause polling duration in millisecond. * @return status of the operation * @throws IllegalArgumentException if timeoutInMs value is invalid * (timeoutinMs > max or timeoutInMs < 0). */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public @PollingStateChangeStatusCode int pausePolling(@DurationMillisLong long timeoutInMs) { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.pausePolling(timeoutInMs), POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE); } /** * Resumes default NFC tag reader mode polling for the current device state if polling is * paused. Calling this while already in polling is a no-op. * @return status of the operation */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public @PollingStateChangeStatusCode int resumePolling() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.resumePolling(), POLLING_STATE_CHANGE_ALREADY_IN_REQUESTED_STATE); } /** * Gets the max pause polling timeout value in millisecond. * @return long integer representing the max timeout */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) @DurationMillisLong public long getMaxPausePollingTimeoutMills() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.getMaxPausePollingTimeoutMs(), 0L); } /** * Set whether to enable auto routing change or not (enabled by default). * If disabled, routing targets are limited to a single off-host destination. * * @param state status of auto routing change, true if enable, otherwise false */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public void setAutoChangeEnabled(boolean state) { NfcAdapter.callService(() -> NfcAdapter.sCardEmulationService.setAutoChangeStatus(state)); } /** * Check if auto routing change is enabled or not. * * @return true if enabled, otherwise false */ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAutoChangeEnabled() { return NfcAdapter.callServiceReturn(() -> NfcAdapter.sCardEmulationService.isAutoChangeEnabled(), false); } /** * Get current routing status * * @return {@link RoutingStatus} indicating the default route, default ISO-DEP * route and default off-host route. */ @NonNull @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) public RoutingStatus getRoutingStatus() { List status = NfcAdapter.callServiceReturn(() -> NfcAdapter.sCardEmulationService.getRoutingStatus(), new ArrayList<>()); return new RoutingStatus(routeStringToInt(status.get(0)), routeStringToInt(status.get(1)), routeStringToInt(status.get(2))); } /** * Overwrites NFC controller routing table, which includes Protocol Route, Technology Route, * and Empty AID Route. * * The parameter set to * {@link ProtocolAndTechnologyRoute#PROTOCOL_AND_TECHNOLOGY_ROUTE_UNSET} * can be used to keep current values for that entry. At least one route should be overridden * when calling this API, otherwise throw {@link IllegalArgumentException}. * * @param protocol ISO-DEP route destination, where the possible inputs are defined in * {@link ProtocolAndTechnologyRoute}. * @param technology Tech-A, Tech-B and Tech-F route destination, where the possible inputs * are defined in * {@link ProtocolAndTechnologyRoute} * @param emptyAid Zero-length AID route destination, where the possible inputs are defined in * {@link ProtocolAndTechnologyRoute} * @param systemCode System Code route destination, where the possible inputs are defined in * {@link ProtocolAndTechnologyRoute} */ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public void overwriteRoutingTable( @CardEmulation.ProtocolAndTechnologyRoute int protocol, @CardEmulation.ProtocolAndTechnologyRoute int technology, @CardEmulation.ProtocolAndTechnologyRoute int emptyAid, @CardEmulation.ProtocolAndTechnologyRoute int systemCode) { String protocolRoute = routeIntToString(protocol); String technologyRoute = routeIntToString(technology); String emptyAidRoute = routeIntToString(emptyAid); String systemCodeRoute = routeIntToString(systemCode); NfcAdapter.callService(() -> NfcAdapter.sCardEmulationService.overwriteRoutingTable( mContext.getUser().getIdentifier(), emptyAidRoute, protocolRoute, technologyRoute, systemCodeRoute )); } /** * Gets current routing table entries. * @return List of {@link NfcRoutingTableEntry} representing current routing table */ @NonNull @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) public List getRoutingTable() { List entryList = NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.getRoutingTableEntryList(), null); List result = new ArrayList<>(); for (Entry entry : entryList) { switch (entry.getType()) { case TYPE_TECHNOLOGY -> result.add( new RoutingTableTechnologyEntry(entry.getNfceeId(), RoutingTableTechnologyEntry.techStringToInt(entry.getEntry()), routeStringToInt(entry.getRoutingType())) ); case TYPE_PROTOCOL -> result.add( new RoutingTableProtocolEntry(entry.getNfceeId(), RoutingTableProtocolEntry.protocolStringToInt(entry.getEntry()), routeStringToInt(entry.getRoutingType())) ); case TYPE_AID -> result.add( new RoutingTableAidEntry(entry.getNfceeId(), entry.getEntry(), routeStringToInt(entry.getRoutingType())) ); case TYPE_SYSTEMCODE -> result.add( new RoutingTableSystemCodeEntry(entry.getNfceeId(), entry.getEntry().getBytes(StandardCharsets.UTF_8), routeStringToInt(entry.getRoutingType())) ); } } return result; } /** * API to force a routing table commit. * @return a {@link StatusCode} to indicate if commit routing succeeded or not */ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) @CommitRoutingStatusCode public int forceRoutingTableCommit() { return NfcAdapter.callServiceReturn( () -> NfcAdapter.sService.commitRouting(), COMMIT_ROUTING_STATUS_FAILED); } /** @hide */ public final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override public void onTagConnected(boolean connected) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(connected, cb::onTagConnected, ex)); } @Override public void onCardEmulationActivated(boolean isActivated) throws RemoteException { mCardEmulationActivated = isActivated; mCallbackMap.forEach((cb, ex) -> handleVoidCallback(isActivated, cb::onCardEmulationActivated, ex)); } @Override public void onRfFieldDetected(boolean isActive) throws RemoteException { mRfFieldActivated = isActive; mCallbackMap.forEach((cb, ex) -> handleVoidCallback(isActive, cb::onRfFieldDetected, ex)); } @Override public void onRfDiscoveryStarted(boolean isDiscoveryStarted) throws RemoteException { mRfDiscoveryStarted = isDiscoveryStarted; mCallbackMap.forEach((cb, ex) -> handleVoidCallback(isDiscoveryStarted, cb::onRfDiscoveryStarted, ex)); } @Override public void onEeListenActivated(boolean isActivated) throws RemoteException { mEeListenActivated = isActivated; mCallbackMap.forEach((cb, ex) -> handleVoidCallback(isActivated, cb::onEeListenActivated, ex)); } @Override public void onEeUpdated() throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(null, (Object input) -> cb.onEeUpdated(), ex)); } @Override public void onStateUpdated(int state) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(state, cb::onStateUpdated, ex)); } @Override public void onApplyRouting(ResultReceiver isSkipped) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isSkipped), cb::onApplyRouting, ex)); } @Override public void onNdefRead(ResultReceiver isSkipped) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isSkipped), cb::onNdefRead, ex)); } @Override public void onEnable(ResultReceiver isAllowed) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isAllowed), cb::onEnableRequested, ex)); } @Override public void onDisable(ResultReceiver isAllowed) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isAllowed), cb::onDisableRequested, ex)); } @Override public void onBootStarted() throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(null, (Object input) -> cb.onBootStarted(), ex)); } @Override public void onEnableStarted() throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(null, (Object input) -> cb.onEnableStarted(), ex)); } @Override public void onDisableStarted() throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(null, (Object input) -> cb.onDisableStarted(), ex)); } @Override public void onBootFinished(int status) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(status, cb::onBootFinished, ex)); } @Override public void onEnableFinished(int status) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(status, cb::onEnableFinished, ex)); } @Override public void onDisableFinished(int status) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(status, cb::onDisableFinished, ex)); } @Override public void onTagDispatch(ResultReceiver isSkipped) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex)); } @Override public void onRoutingChanged(ResultReceiver isSkipped) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback( new ReceiverWrapper<>(isSkipped), cb::onRoutingChanged, ex)); } @Override public void onHceEventReceived(int action) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(action, cb::onHceEventReceived, ex)); } @Override public void onReaderOptionChanged(boolean enabled) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(enabled, cb::onReaderOptionChanged, ex)); } public void onRoutingTableFull() throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(null, (Object input) -> cb.onRoutingTableFull(), ex)); } @Override public void onGetOemAppSearchIntent(List packages, ResultReceiver intentConsumer) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoid2ArgCallback(packages, new ReceiverWrapper<>(intentConsumer), cb::onGetOemAppSearchIntent, ex)); } @Override public void onNdefMessage(Tag tag, NdefMessage message, ResultReceiver hasOemExecutableContent) throws RemoteException { mCallbackMap.forEach((cb, ex) -> { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { ex.execute(() -> cb.onNdefMessage( tag, message, new ReceiverWrapper<>(hasOemExecutableContent))); } catch (RuntimeException exception) { throw exception; } finally { Binder.restoreCallingIdentity(identity); } } }); } @Override public void onLaunchHceAppChooserActivity(String selectedAid, List services, ComponentName failedComponent, String category) throws RemoteException { mCallbackMap.forEach((cb, ex) -> { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { ex.execute(() -> cb.onLaunchHceAppChooserActivity( selectedAid, services, failedComponent, category)); } catch (RuntimeException exception) { throw exception; } finally { Binder.restoreCallingIdentity(identity); } } }); } @Override public void onLaunchHceTapAgainActivity(ApduServiceInfo service, String category) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoid2ArgCallback(service, category, cb::onLaunchHceTapAgainDialog, ex)); } @Override public void onLogEventNotified(OemLogItems item) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoidCallback(item, cb::onLogEventNotified, ex)); } @Override public void onExtractOemPackages(NdefMessage message, ResultReceiver packageConsumer) throws RemoteException { mCallbackMap.forEach((cb, ex) -> handleVoid2ArgCallback(message, new ReceiverWrapper<>(packageConsumer), cb::onExtractOemPackages, ex)); } private void handleVoidCallback( T input, Consumer callbackMethod, Executor executor) { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { executor.execute(() -> callbackMethod.accept(input)); } catch (RuntimeException ex) { throw ex; } finally { Binder.restoreCallingIdentity(identity); } } } private void handleVoid2ArgCallback( T1 input1, T2 input2, BiConsumer callbackMethod, Executor executor) { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); try { executor.execute(() -> callbackMethod.accept(input1, input2)); } catch (RuntimeException ex) { throw ex; } finally { Binder.restoreCallingIdentity(identity); } } } private S handleNonVoidCallbackWithInput( S defaultValue, T input, Function callbackMethod) throws RemoteException { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); S result = defaultValue; try { ExecutorService executor = Executors.newSingleThreadExecutor(); FutureTask futureTask = new FutureTask<>(() -> callbackMethod.apply(input)); var unused = executor.submit(futureTask); try { result = futureTask.get( OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } catch (TimeoutException e) { Log.w(TAG, "Callback timed out: " + callbackMethod); e.printStackTrace(); } finally { executor.shutdown(); } } finally { Binder.restoreCallingIdentity(identity); } return result; } } private T handleNonVoidCallbackWithoutInput(T defaultValue, Supplier callbackMethod) throws RemoteException { synchronized (mLock) { final long identity = Binder.clearCallingIdentity(); T result = defaultValue; try { ExecutorService executor = Executors.newSingleThreadExecutor(); FutureTask futureTask = new FutureTask<>(callbackMethod::get); var unused = executor.submit(futureTask); try { result = futureTask.get( OEM_EXTENSION_RESPONSE_THRESHOLD_MS, TimeUnit.MILLISECONDS); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } catch (TimeoutException e) { Log.w(TAG, "Callback timed out: " + callbackMethod); e.printStackTrace(); } finally { executor.shutdown(); } } finally { Binder.restoreCallingIdentity(identity); } return result; } } } private @CardEmulation.ProtocolAndTechnologyRoute int routeStringToInt(String route) { if (route.equals("DH")) { return PROTOCOL_AND_TECHNOLOGY_ROUTE_DH; } else if (route.startsWith("eSE")) { return PROTOCOL_AND_TECHNOLOGY_ROUTE_ESE; } else if (route.startsWith("SIM")) { return PROTOCOL_AND_TECHNOLOGY_ROUTE_UICC; } else if (route.startsWith("NDEF-NFCEE")) { return PROTOCOL_AND_TECHNOLOGY_ROUTE_NDEF_NFCEE; } else { throw new IllegalStateException("Unexpected value: " + route); } } private class ReceiverWrapper implements Consumer { private final ResultReceiver mResultReceiver; ReceiverWrapper(ResultReceiver resultReceiver) { mResultReceiver = resultReceiver; } @Override public void accept(T result) { if (result instanceof Boolean) { mResultReceiver.send((Boolean) result ? 1 : 0, null); } else if (result instanceof Intent) { Bundle bundle = new Bundle(); bundle.putParcelable("intent", (Intent) result); mResultReceiver.send(0, bundle); } else if (result instanceof List list) { if (list.stream().allMatch(String.class::isInstance)) { Bundle bundle = new Bundle(); bundle.putStringArray("packageNames", list.stream().map(pkg -> (String) pkg).toArray(String[]::new)); mResultReceiver.send(0, bundle); } } } @Override public Consumer andThen(Consumer after) { return Consumer.super.andThen(after); } } }