diff options
| author | 2023-09-01 11:30:09 -0400 | |
|---|---|---|
| committer | 2023-11-14 10:50:41 -0800 | |
| commit | b8c4f9bf2f3ddd11422f23e5f822760a5c4ef118 (patch) | |
| tree | 93172591907ae5a31af7f08e6d67159ca56f7d60 | |
| parent | 58ccf443eb7d8cf263c6bd51875264866cf72d37 (diff) | |
Implementing support for NFC observe mode, polling loop fingerprints
and field strength along with their associated APIs.
Bug: 294217286
Bug: 294435374
Bug: 296057223
Test: tested with CTS and manually
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:81080d559182483d9582dc8dc9ed75de3307c572)
Merged-In: I884527f9271a93997fa47b3764f9fa711cddb6f2
Change-Id: I884527f9271a93997fa47b3764f9fa711cddb6f2
| -rw-r--r-- | AconfigFlags.bp | 12 | ||||
| -rw-r--r-- | core/api/current.txt | 15 | ||||
| -rw-r--r-- | core/java/android/nfc/INfcAdapter.aidl | 2 | ||||
| -rw-r--r-- | core/java/android/nfc/INfcCardEmulation.aidl | 1 | ||||
| -rw-r--r-- | core/java/android/nfc/NfcAdapter.java | 55 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/CardEmulation.java | 18 | ||||
| -rw-r--r-- | core/java/android/nfc/cardemulation/HostApduService.java | 118 | ||||
| -rw-r--r-- | core/java/android/nfc/flags.aconfig | 28 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 6 |
9 files changed, 255 insertions, 0 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 45f65709533f..e0745faecf5a 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -121,6 +121,18 @@ aconfig_declarations { srcs: ["core/java/android/nfc/*.aconfig"], } +cc_aconfig_library { + name: "android_nfc_flags_aconfig_c_lib", + vendor_available: true, + aconfig_declarations: "android.nfc.flags-aconfig", + apex_available: [ + "//apex_available:platform", + "com.android.nfcservices", + "nfc_nci.st21nfc.default", + ], + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + java_aconfig_library { name: "android.nfc.flags-aconfig-java", aconfig_declarations: "android.nfc.flags-aconfig", diff --git a/core/api/current.txt b/core/api/current.txt index c9cba0065dee..e392bd3d2c11 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -29105,14 +29105,17 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean allowTransaction(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean disallowTransaction(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); method @Nullable public android.nfc.NfcAntennaInfo getNfcAntennaInfo(); method public boolean ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler); method public boolean isEnabled(); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean isObserveModeSupported(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionEnabled(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") public boolean isReaderOptionSupported(); method public boolean isSecureNfcEnabled(); @@ -29220,6 +29223,7 @@ package android.nfc.cardemulation { method public boolean removeAidsForService(android.content.ComponentName, String); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String); method public boolean setPreferredService(android.app.Activity, android.content.ComponentName); + method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setServiceObserveModeDefault(@NonNull android.content.ComponentName, boolean); method public boolean supportsAidPrefixRegistration(); method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName); method public boolean unsetPreferredService(android.app.Activity); @@ -29239,9 +29243,20 @@ package android.nfc.cardemulation { method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.os.Bundle>); method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_A = 65; // 0x0041 'A' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_B = 66; // 0x0042 'B' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_F = 70; // 0x0046 'F' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_OFF = 88; // 0x0058 'X' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_ON = 79; // 0x004f 'O' + field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final char POLLING_LOOP_TYPE_UNKNOWN = 85; // 0x0055 'U' field public static final String SERVICE_INTERFACE = "android.nfc.cardemulation.action.HOST_APDU_SERVICE"; field public static final String SERVICE_META_DATA = "android.nfc.cardemulation.host_apdu_service"; } diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 0c95c2ec7a7a..f6beec179d57 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -84,4 +84,6 @@ interface INfcAdapter boolean isReaderOptionSupported(); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean enableReaderOption(boolean enable); + boolean isObserveModeSupported(); + boolean setObserveMode(boolean enabled); } diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl index c7b3b2c03f65..191385a3c13d 100644 --- a/core/java/android/nfc/INfcCardEmulation.aidl +++ b/core/java/android/nfc/INfcCardEmulation.aidl @@ -30,6 +30,7 @@ interface INfcCardEmulation boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid); boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category); boolean setDefaultForNextTap(int userHandle, in ComponentName service); + boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index c89759553810..98a980f5e7f8 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -1081,6 +1081,61 @@ public final class NfcAdapter { } } + + /** + * Returns whether the device supports observer mode or not. When observe + * mode is enabled, the NFC hardware will listen for NFC readers, but not + * respond to them. When observe mode is disabled, the NFC hardware will + * resoond to the reader and proceed with the transaction. + * @return true if the mode is supported, false otherwise. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean isObserveModeSupported() { + try { + return sService.isObserveModeSupported(); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Disables observe mode to allow the transaction to proceed. See + * {@link #isObserveModeSupported()} for a description of observe mode and + * use {@link #disallowTransaction()} to enable observe mode and block + * transactions again. + * + * @return boolean indicating success or failure. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean allowTransaction() { + try { + return sService.setObserveMode(false); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + + /** + * Signals that the transaction has completed and observe mode may be + * reenabled. See {@link #isObserveModeSupported()} for a description of + * observe mode and use {@link #allowTransaction()} to disable observe + * mode and allow transactions to proceed. + * + * @return boolean indicating success or failure. + */ + + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean disallowTransaction() { + try { + return sService.setObserveMode(true); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + return false; + } + } + /** * Resumes default polling for the current device state if polling is paused. Calling * this while polling is not paused is a no-op. diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java index d048b595ad1e..58b6179691e9 100644 --- a/core/java/android/nfc/cardemulation/CardEmulation.java +++ b/core/java/android/nfc/cardemulation/CardEmulation.java @@ -328,6 +328,24 @@ public final class CardEmulation { return SELECTION_MODE_ASK_IF_CONFLICT; } } + /** + * Sets whether the system should default to observe mode or not when + * the service is in the foreground or the default payment service. + * + * @param service The component name of the service + * @param enable Whether the servic should default to observe mode or not + * @return whether the change was successful. + */ + @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) + public boolean setServiceObserveModeDefault(@NonNull ComponentName service, boolean enable) { + try { + return sService.setServiceObserveModeDefault(mContext.getUser().getIdentifier(), + service, enable); + } catch (RemoteException e) { + Log.e(TAG, "Failed to reach CardEmulationService."); + } + return false; + } /** * Registers a list of AIDs for a specific category for the diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index 55d0e73780a2..7cd2533a7dbf 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -16,11 +16,14 @@ package android.nfc.cardemulation; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -29,6 +32,9 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; +import java.util.ArrayList; +import java.util.List; + /** * <p>HostApduService is a convenience {@link Service} class that can be * extended to emulate an NFC card inside an Android @@ -230,9 +236,99 @@ public abstract class HostApduService extends Service { /** * @hide */ + public static final int MSG_POLLING_LOOP = 4; + + /** + * @hide + */ public static final String KEY_DATA = "data"; /** + * POLLING_LOOP_TYPE_KEY is the Bundle key for the type of + * polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TYPE_KEY = "android.nfc.cardemulation.TYPE"; + + /** + * POLLING_LOOP_TYPE_A is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-A. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_A = 'A'; + + /** + * POLLING_LOOP_TYPE_B is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-B. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_B = 'B'; + + /** + * POLLING_LOOP_TYPE_F is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop is for NFC-F. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_F = 'F'; + + /** + * POLLING_LOOP_TYPE_ON is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns on. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_ON = 'O'; + + /** + * POLLING_LOOP_TYPE_OFF is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop turns off. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_OFF = 'X'; + + /** + * POLLING_LOOP_TYPE_UNKNOWN is the value associated with the key + * POLLING_LOOP_TYPE in the Bundle passed to {@link #processPollingFrames(List)} + * when the polling loop frame isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final char POLLING_LOOP_TYPE_UNKNOWN = 'U'; + + /** + * POLLING_LOOP_DATA is the Bundle key for the raw data of captured from + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_DATA_KEY = "android.nfc.cardemulation.DATA"; + + /** + * POLLING_LOOP_GAIN_KEY is the Bundle key for the field strength of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_GAIN_KEY = "android.nfc.cardemulation.GAIN"; + + /** + * POLLING_LOOP_TIMESTAMP_KEY is the Bundle key for the timestamp of + * the polling loop frame in the Bundle passed to {@link #processPollingFrames(List)} + * when the frame type isn't recognized. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public static final String POLLING_LOOP_TIMESTAMP_KEY = "android.nfc.cardemulation.TIMESTAMP"; + + /** + * @hide + */ + public static final String POLLING_LOOP_FRAMES_BUNDLE_KEY = + "android.nfc.cardemulation.POLLING_FRAMES"; + + /** * Messenger interface to NfcService for sending responses. * Only accessed on main thread by the message handler. * @@ -255,6 +351,7 @@ public abstract class HostApduService extends Service { byte[] apdu = dataBundle.getByteArray(KEY_DATA); if (apdu != null) { + HostApduService has = HostApduService.this; byte[] responseApdu = processCommandApdu(apdu, null); if (responseApdu != null) { if (mNfcService == null) { @@ -306,6 +403,12 @@ public abstract class HostApduService extends Service { Log.e(TAG, "RemoteException calling into NfcService."); } break; + case MSG_POLLING_LOOP: + ArrayList<Bundle> frames = + msg.getData().getParcelableArrayList(POLLING_LOOP_FRAMES_BUNDLE_KEY, + Bundle.class); + processPollingFrames(frames); + break; default: super.handleMessage(msg); } @@ -366,6 +469,21 @@ public abstract class HostApduService extends Service { } } + /** + * This method is called when a polling frame has been received from a + * remote device. If the device is in observe mode, the service should + * call {@link NfcAdapter#allowTransaction()} once it is ready to proceed + * with the transaction. If the device is not in observe mode, the service + * can use this polling frame information to determine how to proceed if it + * subsequently has {@link #processCommandApdu(byte[], Bundle)} called. The + * service must override this method inorder to receive polling frames, + * otherwise the base implementation drops the frame. + * + * @param frame A description of the polling frame. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + public void processPollingFrames(@NonNull List<Bundle> frame) { + } /** * <p>This method will be called when a command APDU has been received diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig index cd50ace036de..17e042761dbe 100644 --- a/core/java/android/nfc/flags.aconfig +++ b/core/java/android/nfc/flags.aconfig @@ -20,3 +20,31 @@ flag { description: "Flag for NFC user restriction" bug: "291187960" } + +flag { + name: "nfc_observe_mode" + namespace: "nfc" + description: "Enable NFC Observe Mode" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications" + bug: "294217286" +} + +flag { + name: "nfc_observe_mode_st_shim" + namespace: "nfc" + description: "Enable NFC Observe Mode ST shim" + bug: "294217286" +} + +flag { + name: "nfc_read_polling_loop_st_shim" + namespace: "nfc" + description: "Enable NFC Polling Loop Notifications ST shim" + bug: "294217286" +} diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6f7bc53e891c..3413282351ff 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4303,6 +4303,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is true.--> <attr name="requireDeviceScreenOn" format="boolean"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode" format="boolean"/> </declare-styleable> <!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that @@ -4327,6 +4330,9 @@ <!-- Whether the device must be screen on before routing data to this service. The default is false.--> <attr name="requireDeviceScreenOn"/> + <!-- Whether the device should default to observe mode when this service is + default or in the foreground. --> + <attr name="defaultToObserveMode"/> </declare-styleable> <!-- Specify one or more <code>aid-group</code> elements inside a |