diff options
| author | 2024-07-25 03:30:22 +0000 | |
|---|---|---|
| committer | 2024-07-25 03:30:22 +0000 | |
| commit | e90a4d263130ca8006faf2f5f48fba93152d7744 (patch) | |
| tree | 192d14393d74963fd5233ee7ebca2e31d76d21cd | |
| parent | c90540ca1aecb757f36623494373f0e5dec854c3 (diff) | |
| parent | 5fd321ce8975ca529ea32dd00310209b782fa359 (diff) | |
Merge changes from topic "nfc_aosp_main_merge" into main
* changes:
Adding wallet payment service to log protos for nfc.
[nfc] Add disallow nfc change user restriction.
Change NFC antenna api comments to indicated top-left start point for all axis.
Eliminate boilder plate code for recovering service in NfcAdapter
Add Test method to inject HCE data
[framework] Add a flag for nfc persist log.
Add NFC observe mode + auto-transact dumpsys protos
Add NFC observe mode + auto-transact dumpsys logging
Fix PollingFrame timestamp documentation
Set default discovery technology
[framework] Add Nfc oem extension api surface.
Pass package name to `NfcService.setObserveMode()`
[framework] Expose enable()/disable() as public api.
Add POLLING_LOOP_TYPE_UNKNOWN to PollingFrameType
Offhost NFC services shouldn't be able to register non-autotransact polling loop filters
Document that only the currently preferred service can enable and disable observe mode
nfc(api): Move @hide constants to Constants.java
Use PollingFrame class instead of Bundles to represent polling frames in NFC stack
Make NFC library available to multidevice CTS NFC tests
nfc(api): Link against framework-permission
KEY_POLLING_LOOP_TIMESTAMP is a long not an int
Un-deprecate some NFC methods
Reword autoTransact javadoc
nfc(api): Change min_sdk to current for framework-nfc
Revert^3 "nfc(api): Change min_sdk to V for framework-nfc"
[nfc] Pass in package name to nfcService when enabling nfc.
Add javadoc for autoTransact parameter
Revert^2 "nfc(api): Change min_sdk to V for framework-nfc"
Use long for timestamp in PollingFrame
Revert "nfc(api): Change min_sdk to V for framework-nfc"
nfc(api): Change min_sdk to V for framework-nfc
Add API for polling loop pattern filters based on developer feedback.
Add Javadoc for PollingFrame constructor
Provide information on whether a polling frame is auto-transact
Treat gain as an unsigned int
Rename getGain() to getVendorSpecificGain()
Deprecate methods on CardEmulation in favor of Wallet Role.
23 files changed, 719 insertions, 474 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index d64593854749..77cb03e86c8a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -34045,6 +34045,7 @@ package android.os { field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing"; field public static final String DISALLOW_CAMERA_TOGGLE = "disallow_camera_toggle"; field public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g"; + field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO = "no_change_near_field_communication_radio"; field public static final String DISALLOW_CHANGE_WIFI_STATE = "no_change_wifi_state"; field public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth"; field public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 805cfb7b115d..02c88e2bd960 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10405,6 +10405,7 @@ package android.nfc.cardemulation { @FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable { ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String, boolean); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopPatternFilter(@NonNull String, boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream); @@ -10416,6 +10417,7 @@ package android.nfc.cardemulation { method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement(); method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters(); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.util.regex.Pattern> getPollingLoopPatternFilters(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids(); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName(); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean getShouldAutoTransact(@NonNull String); @@ -10430,6 +10432,7 @@ package android.nfc.cardemulation { method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager); method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopPatternFilter(@NonNull String); method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock(); method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement(); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 9757a1096a30..599c6a5fe3c0 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1900,6 +1900,31 @@ public class UserManager { "no_near_field_communication_radio"; /** + * This user restriction specifies if Near-field communication is disallowed to change + * on the device. If Near-field communication is disallowed it cannot be changed via Settings. + * + * <p>This restriction can only be set by a device owner or a profile owner of an + * organization-owned managed profile on the parent profile. + * In both cases, the restriction applies globally on the device and will not allow Near-field + * communication state being changed. + * + * <p> + * Near-field communication (NFC) is a radio technology that allows two devices (like your phone + * and a payments terminal) to communicate with each other when they're close together. + * + * <p>Default is <code>false</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION) + public static final String DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO = + "no_change_near_field_communication_radio"; + + /** * This user restriction specifies if Thread network is disallowed on the device. If Thread * network is disallowed it cannot be turned on via Settings. * @@ -2056,6 +2081,7 @@ public class UserManager { DISALLOW_WIFI_DIRECT, DISALLOW_ADD_WIFI_CONFIG, DISALLOW_CELLULAR_2G, + DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO, DISALLOW_ULTRA_WIDEBAND_RADIO, DISALLOW_GRANT_ADMIN, DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, diff --git a/core/proto/android/nfc/apdu_service_info.proto b/core/proto/android/nfc/apdu_service_info.proto index fd110c44483c..9efdfcbea3d3 100644 --- a/core/proto/android/nfc/apdu_service_info.proto +++ b/core/proto/android/nfc/apdu_service_info.proto @@ -27,6 +27,20 @@ option java_multiple_files = true; message ApduServiceInfoProto { option (.android.msg_privacy).dest = DEST_EXPLICIT; + message AutoTransactMapping { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional string aid = 1; + optional bool should_auto_transact = 2; + } + + message AutoTransactPattern { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + + optional string regexp_pattern = 1; + optional bool should_auto_transact = 2; + } + optional .android.content.ComponentNameProto component_name = 1; optional string description = 2; optional bool on_host = 3; @@ -35,4 +49,7 @@ message ApduServiceInfoProto { repeated AidGroupProto static_aid_groups = 6; repeated AidGroupProto dynamic_aid_groups = 7; optional string settings_activity_name = 8; + optional bool should_default_to_observe_mode = 9; + repeated AutoTransactMapping auto_transact_mapping = 10; + repeated AutoTransactPattern auto_transact_patterns = 11; } diff --git a/core/proto/android/nfc/card_emulation.proto b/core/proto/android/nfc/card_emulation.proto index 9c3c6d704922..81da30dd8bbf 100644 --- a/core/proto/android/nfc/card_emulation.proto +++ b/core/proto/android/nfc/card_emulation.proto @@ -59,6 +59,7 @@ message PreferredServicesProto { optional .android.content.ComponentNameProto foreground_requested = 5; optional .android.content.ComponentNameProto settings_default = 6; optional bool prefer_foreground = 7; + optional .android.content.ComponentNameProto wallet_role_holder_payment_service = 8; } // Debugging information for com.android.nfc.cardemulation.EnabledNfcFServices diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 48cf09a84e57..f3cca8b1211a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4472,8 +4472,8 @@ </declare-styleable> <!-- Specify one or more <code>polling-loop-filter</code> elements inside a - <code>host-apdu-service</code> to indicate polling loop frames that - your service can handle. --> + <code>host-apdu-service</code> or <code>offhost-apdu-service</code> to indicate polling + loop frames that your service can handle. --> <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") --> <declare-styleable name="PollingLoopFilter"> <!-- The polling loop frame. This attribute is mandatory. --> @@ -4484,6 +4484,21 @@ <attr name="autoTransact" format="boolean"/> </declare-styleable> + <!-- Specify one or more <code>polling-loop-pattern-filter</code> elements inside a + <code>host-apdu-service</code> or <code>offhost-apdu-service</code> to indicate polling + loop frames that your service can handle. --> + <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") --> + <declare-styleable name="PollingLoopPatternFilter"> + <!-- The patter to match polling loop frames to, must to be compatible with + {@link java.util.regex.Pattern#compile(String)} and only contain hexadecimal numbers and + `.`, `?` and `*` operators. This attribute is mandatory. --> + <attr name="name" /> + <!-- Whether or not the system should automatically start a transaction when this polling + loop filter matches. If not set, default value is false. --> + <!-- @FlaggedApi("android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP") --> + <attr name="autoTransact" format="boolean"/> + </declare-styleable> + <!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that describes an {@link android.nfc.cardemulation.HostNfcFService} service, which is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA} diff --git a/nfc/Android.bp b/nfc/Android.bp index 2a01b3fec088..0282e6f5c246 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -38,6 +38,8 @@ java_sdk_library { name: "framework-nfc", libs: [ "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage + "framework-permission-s", + "framework-permission", ], static_libs: [ "android.nfc.flags-aconfig-java", @@ -49,7 +51,7 @@ java_sdk_library { ], defaults: ["framework-module-defaults"], sdk_version: "module_current", - min_sdk_version: "34", // should be 35 (making it 34 for compiling for `-next`) + min_sdk_version: "current", installable: true, optimize: { enabled: false, @@ -61,6 +63,7 @@ java_sdk_library { ], impl_library_visibility: [ "//frameworks/base:__subpackages__", + "//cts/hostsidetests/multidevices/nfc:__subpackages__", "//cts/tests/tests/nfc", "//packages/apps/Nfc:__subpackages__", ], diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 9d0221a3ae68..cf7aea405756 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -64,8 +64,10 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); 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); @@ -205,7 +207,10 @@ package android.nfc.cardemulation { method public boolean isDefaultServiceForCategory(android.content.ComponentName, String); method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>); method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean); method public boolean removeAidsForService(android.content.ComponentName, String); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean removePollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String); + method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean removePollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull 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 setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean); @@ -265,12 +270,12 @@ package android.nfc.cardemulation { } @FlaggedApi("android.nfc.nfc_read_polling_loop") public final class PollingFrame implements android.os.Parcelable { - ctor public PollingFrame(int, @Nullable byte[], int, int); method public int describeContents(); method @NonNull public byte[] getData(); - method public int getGain(); - method public int getTimestamp(); + method public long getTimestamp(); + method public boolean getTriggeredAutoTransact(); method public int getType(); + method public int getVendorSpecificGain(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR; field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41 diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 310130e59bfe..3375e18c001d 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -3,9 +3,7 @@ package android.nfc { public final class NfcAdapter { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState(); @@ -29,6 +27,7 @@ package android.nfc { field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC"; field @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.SHOW_CUSTOMIZED_RESOLVER) public static final String ACTION_SHOW_NFC_RESOLVER = "android.nfc.action.SHOW_NFC_RESOLVER"; field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String EXTRA_RESOLVE_INFOS = "android.nfc.extra.RESOLVE_INFOS"; + field @FlaggedApi("android.nfc.nfc_set_default_disc_tech") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final int FLAG_SET_DEFAULT_TECH = 1073741824; // 0x40000000 field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1 field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3 field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2 @@ -58,7 +57,9 @@ package android.nfc { @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension { method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference(); + method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback); + method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback); } diff --git a/nfc/java/android/nfc/AvailableNfcAntenna.java b/nfc/java/android/nfc/AvailableNfcAntenna.java index 6e6512a04971..e76aeb07f106 100644 --- a/nfc/java/android/nfc/AvailableNfcAntenna.java +++ b/nfc/java/android/nfc/AvailableNfcAntenna.java @@ -28,13 +28,13 @@ import android.os.Parcelable; public final class AvailableNfcAntenna implements Parcelable { /** * Location of the antenna on the Y axis in millimeters. - * 0 is the bottom-left when the user is facing the screen + * 0 is the top-left when the user is facing the screen * and the device orientation is Portrait. */ private final int mLocationX; /** * Location of the antenna on the Y axis in millimeters. - * 0 is the bottom-left when the user is facing the screen + * 0 is the top-left when the user is facing the screen * and the device orientation is Portrait. */ private final int mLocationY; @@ -46,7 +46,7 @@ public final class AvailableNfcAntenna implements Parcelable { /** * Location of the antenna on the X axis in millimeters. - * 0 is the bottom-left when the user is facing the screen + * 0 is the top-left when the user is facing the screen * and the device orientation is Portrait. */ public int getLocationX() { @@ -55,7 +55,7 @@ public final class AvailableNfcAntenna implements Parcelable { /** * Location of the antenna on the Y axis in millimeters. - * 0 is the bottom-left when the user is facing the screen + * 0 is the top-left when the user is facing the screen * and the device orientation is Portrait. */ public int getLocationY() { diff --git a/nfc/java/android/nfc/Constants.java b/nfc/java/android/nfc/Constants.java index f76833063605..9b11e2d30ed8 100644 --- a/nfc/java/android/nfc/Constants.java +++ b/nfc/java/android/nfc/Constants.java @@ -16,6 +16,8 @@ package android.nfc; +import android.provider.Settings; + /** * @hide * TODO(b/303286040): Holds @hide API constants. Formalize these APIs. @@ -26,4 +28,15 @@ public final class Constants { public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground"; public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any"; + + /** + * @hide constant copied from {@link Settings.Global} + * TODO(b/274636414): Migrate to official API in Android V. + */ + public static final String SETTINGS_SATELLITE_MODE_RADIOS = "satellite_mode_radios"; + /** + * @hide constant copied from {@link Settings.Global} + * TODO(b/274636414): Migrate to official API in Android V. + */ + public static final String SETTINGS_SATELLITE_MODE_ENABLED = "satellite_mode_enabled"; } diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index e151a0e4d6e2..90ca92f299a4 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -35,6 +35,7 @@ import android.nfc.INfcDta; import android.nfc.INfcWlcStateListener; import android.nfc.NfcAntennaInfo; import android.nfc.WlcListenerDeviceInfo; +import android.nfc.cardemulation.PollingFrame; import android.os.Bundle; /** @@ -48,8 +49,8 @@ interface INfcAdapter INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); INfcDta getNfcDtaInterface(in String pkg); int getState(); - boolean disable(boolean saveState); - boolean enable(); + boolean disable(boolean saveState, in String pkg); + boolean enable(in String pkg); void pausePolling(int timeoutInMs); void resumePolling(); @@ -90,7 +91,7 @@ interface INfcAdapter boolean enableReaderOption(boolean enable); boolean isObserveModeSupported(); boolean isObserveModeEnabled(); - boolean setObserveMode(boolean enabled); + boolean setObserveMode(boolean enabled, String pkg); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean setWlcEnabled(boolean enable); @@ -101,12 +102,15 @@ interface INfcAdapter void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags); - void notifyPollingLoop(in Bundle frame); + void notifyPollingLoop(in PollingFrame frame); void notifyHceDeactivated(); + void notifyTestHceData(in int technology, in byte[] data); int sendVendorNciMessage(int mt, int gid, int oid, in byte[] payload); void registerVendorExtensionCallback(in INfcVendorNciCallback callbacks); void unregisterVendorExtensionCallback(in INfcVendorNciCallback callbacks); void registerOemExtensionCallback(INfcOemExtensionCallback callbacks); void unregisterOemExtensionCallback(INfcOemExtensionCallback callbacks); void clearPreference(); + void setScreenState(); + void checkFirmware(); } diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl index 85a07b74871b..cb97f23e813c 100644 --- a/nfc/java/android/nfc/INfcCardEmulation.aidl +++ b/nfc/java/android/nfc/INfcCardEmulation.aidl @@ -33,10 +33,13 @@ interface INfcCardEmulation boolean setShouldDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable); boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup); boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter, boolean autoTransact); + boolean registerPollingLoopPatternFilterForService(int userHandle, in ComponentName service, in String pollingLoopPatternFilter, boolean autoTransact); boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement); boolean unsetOffHostForService(int userHandle, in ComponentName service); AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category); boolean removeAidGroupForService(int userHandle, in ComponentName service, String category); + boolean removePollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter); + boolean removePollingLoopPatternFilterForService(int userHandle, in ComponentName service, in String pollingLoopPatternFilter); List<ApduServiceInfo> getServices(int userHandle, in String category); boolean setPreferredService(in ComponentName service); boolean unsetPreferredService(); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index fe78c9b23353..395f81d73351 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -340,7 +340,8 @@ public final class NfcAdapter { public static final int FLAG_READER_NFC_BARCODE = 0x10; /** @hide */ - @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = { + @IntDef(flag = true, value = { + FLAG_SET_DEFAULT_TECH, FLAG_READER_KEEP, FLAG_READER_DISABLE, FLAG_READER_NFC_A, @@ -438,7 +439,8 @@ public final class NfcAdapter { public static final int FLAG_USE_ALL_TECH = 0xff; /** @hide */ - @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = { + @IntDef(flag = true, value = { + FLAG_SET_DEFAULT_TECH, FLAG_LISTEN_KEEP, FLAG_LISTEN_DISABLE, FLAG_LISTEN_NFC_PASSIVE_A, @@ -449,6 +451,18 @@ public final class NfcAdapter { public @interface ListenTechnology {} /** + * Flag used in {@link #setDiscoveryTechnology(Activity, int, int)}. + * <p> + * Setting this flag changes the default listen or poll tech. + * Only available to privileged apps. + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH) + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + public static final int FLAG_SET_DEFAULT_TECH = 0x40000000; + + /** * @hide * @removed */ @@ -949,22 +963,9 @@ public final class NfcAdapter { throw new UnsupportedOperationException("You need a context on NfcAdapter to use the " + " NFC extras APIs"); } - try { - return sService.getNfcDtaInterface(mContext.getPackageName()); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return null; - } - try { - return sService.getNfcDtaInterface(mContext.getPackageName()); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return null; - } + return callServiceReturn(() -> sService.getNfcDtaInterface(mContext.getPackageName()), + null); + } /** @@ -1081,22 +1082,8 @@ public final class NfcAdapter { @SystemApi @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) public @AdapterState int getAdapterState() { - try { - return sService.getState(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return NfcAdapter.STATE_OFF; - } - try { - return sService.getState(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return NfcAdapter.STATE_OFF; - } + return callServiceReturn(() -> sService.getState(), NfcAdapter.STATE_OFF); + } /** @@ -1106,6 +1093,9 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already on, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then @@ -1113,27 +1103,12 @@ public final class NfcAdapter { * NFC on (for example we are in airplane mode and NFC is not * toggleable in airplane mode on this platform). * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable() { - try { - return sService.enable(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.enable(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.enable(mContext.getPackageName()), false); + } /** @@ -1146,33 +1121,22 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already off, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then * there is some problem that prevents an attempt to turn * NFC off. * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable() { - try { - return sService.disable(true); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.disable(true); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.disable(true, mContext.getPackageName()), + false); + } /** @@ -1182,22 +1146,9 @@ public final class NfcAdapter { @SystemApi @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean persist) { - try { - return sService.disable(persist); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.disable(persist); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.disable(persist, mContext.getPackageName()), + false); + } /** @@ -1223,12 +1174,7 @@ public final class NfcAdapter { */ @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) public boolean isObserveModeSupported() { - try { - return sService.isObserveModeSupported(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - return false; - } + return callServiceReturn(() -> sService.isObserveModeSupported(), false); } /** @@ -1239,18 +1185,17 @@ public final class NfcAdapter { @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) public boolean isObserveModeEnabled() { - try { - return sService.isObserveModeEnabled(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - return false; - } + return callServiceReturn(() -> sService.isObserveModeEnabled(), false); } /** * Controls whether the NFC adapter will allow transactions to proceed or be in observe mode * and simply observe and notify the APDU service of polling loop frames. See - * {@link #isObserveModeSupported()} for a description of observe mode. + * {@link #isObserveModeSupported()} for a description of observe mode. Only the package of the + * currently preferred service (the service set as preferred by the current foreground + * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the + * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}), + * otherwise a call to this method will fail and return false. * * @param enabled false disables observe mode to allow the transaction to proceed while true * enables observe mode and does not allow transactions to proceed. @@ -1260,12 +1205,12 @@ public final class NfcAdapter { @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) public boolean setObserveModeEnabled(boolean enabled) { - try { - return sService.setObserveMode(enabled); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - return false; + if (mContext == null) { + throw new UnsupportedOperationException("You need a context on NfcAdapter to use the " + + " observe mode APIs"); } + return callServiceReturn(() -> sService.setObserveMode(enabled, mContext.getPackageName()), + false); } /** @@ -1862,14 +1807,6 @@ public final class NfcAdapter { public void setDiscoveryTechnology(@NonNull Activity activity, @PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) { - // A special treatment of the _KEEP flags - if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) { - listenTechnology = -1; - } - if ((pollTechnology & FLAG_READER_KEEP) != 0) { - pollTechnology = -1; - } - if (listenTechnology == FLAG_LISTEN_DISABLE) { synchronized (sLock) { if (!sHasNfcFeature) { @@ -1889,7 +1826,25 @@ public final class NfcAdapter { } } } - mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology); + /* + * Privileged FLAG to set technology mask for all data processed by NFC controller + * Note: Use with caution! The app is responsible for ensuring that the discovery + * technology mask is returned to default. + * Note: FLAG_USE_ALL_TECH used with _KEEP flags will reset the technolody to android default + */ + if (Flags.nfcSetDefaultDiscTech() + && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH + || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) { + Binder token = new Binder(); + try { + NfcAdapter.sService.updateDiscoveryTechnology(token, + pollTechnology, listenTechnology); + } catch (RemoteException e) { + attemptDeadServiceRecovery(e); + } + } else { + mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology); + } } /** @@ -2021,22 +1976,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.setNfcSecure(enable); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.setNfcSecure(enable); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.setNfcSecure(enable), false); + } /** @@ -2052,22 +1993,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.deviceSupportsNfcSecure(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.deviceSupportsNfcSecure(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.deviceSupportsNfcSecure(), false); + } /** @@ -2085,22 +2012,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.getNfcAntennaInfo(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return null; - } - try { - return sService.getNfcAntennaInfo(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return null; - } + return callServiceReturn(() -> sService.getNfcAntennaInfo(), null); + } /** @@ -2118,22 +2031,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isNfcSecureEnabled(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isNfcSecureEnabled(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isNfcSecureEnabled(), false); + } /** @@ -2149,22 +2048,8 @@ public final class NfcAdapter { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.enableReaderOption(enable); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.enableReaderOption(enable); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.enableReaderOption(enable), false); + } /** @@ -2178,22 +2063,8 @@ public final class NfcAdapter { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isReaderOptionSupported(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isReaderOptionSupported(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isReaderOptionSupported(), false); + } /** @@ -2209,22 +2080,8 @@ public final class NfcAdapter { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isReaderOptionEnabled(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isReaderOptionEnabled(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isReaderOptionEnabled(), false); + } /** @@ -2352,11 +2209,9 @@ public final class NfcAdapter { synchronized (mLock) { mTagRemovedListener = iListener; } - try { - return sService.ignore(tag.getServiceHandle(), debounceMs, iListener); - } catch (RemoteException e) { - return false; - } + final ITagRemovedCallback.Stub passedListener = iListener; + return callServiceReturn(() -> + sService.ignore(tag.getServiceHandle(), debounceMs, passedListener), false); } /** @@ -2473,22 +2328,9 @@ public final class NfcAdapter { throw new UnsupportedOperationException("You need a context on NfcAdapter to use the " + " NFC extras APIs"); } - try { - return sService.getNfcAdapterExtrasInterface(mContext.getPackageName()); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return null; - } - try { - return sService.getNfcAdapterExtrasInterface(mContext.getPackageName()); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return null; - } + return callServiceReturn(() -> + sService.getNfcAdapterExtrasInterface(mContext.getPackageName()), null); + } void enforceResumed(Activity activity) { @@ -2533,22 +2375,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.setControllerAlwaysOn(value); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.setControllerAlwaysOn(value); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.setControllerAlwaysOn(value), false); + } /** @@ -2564,22 +2392,8 @@ public final class NfcAdapter { @SystemApi @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn() { - try { - return sService.isControllerAlwaysOn(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isControllerAlwaysOn(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isControllerAlwaysOn(), false); + } /** @@ -2598,22 +2412,8 @@ public final class NfcAdapter { if (!sHasNfcFeature && !sHasCeFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isControllerAlwaysOnSupported(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isControllerAlwaysOnSupported(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isControllerAlwaysOnSupported(), false); + } /** @@ -2683,21 +2483,9 @@ public final class NfcAdapter { Log.e(TAG, "TagIntentAppPreference is not supported"); throw new UnsupportedOperationException(); } - try { - return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - } - try { - return sService.setTagIntentAppPreferenceForUser(userId, pkg, allow); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE; - } + return callServiceReturn(() -> + sService.setTagIntentAppPreferenceForUser(userId, pkg, allow), + TAG_INTENT_APP_PREF_RESULT_UNAVAILABLE); } @@ -2772,22 +2560,8 @@ public final class NfcAdapter { if (!sHasNfcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isTagIntentAppPreferenceSupported(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isTagIntentAppPreferenceSupported(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isTagIntentAppPreferenceSupported(), false); + } /** @@ -2800,12 +2574,30 @@ public final class NfcAdapter { @TestApi @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public void notifyPollingLoop(@NonNull PollingFrame pollingFrame) { - Bundle frame = pollingFrame.toBundle(); + callService(() -> sService.notifyPollingLoop(pollingFrame)); + } + + + /** + * Notifies the system of new HCE data for tests. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void notifyTestHceData(int technology, byte[] data) { + callService(() -> sService.notifyTestHceData(technology, data)); + } + + interface ServiceCall { + void call() throws RemoteException; + } + + void callService(ServiceCall call) { try { if (sService == null) { attemptDeadServiceRecovery(null); } - sService.notifyPollingLoop(frame); + call.call(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time @@ -2814,39 +2606,46 @@ public final class NfcAdapter { return; } try { - sService.notifyPollingLoop(frame); + call.call(); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover NFC Service."); } } } - - /** - * Notifies the system of a an HCE session being deactivated. - * * - * @hide - */ - @TestApi - @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) - public void notifyHceDeactivated() { + interface ServiceCallReturn<T> { + T call() throws RemoteException; + } + <T> T callServiceReturn(ServiceCallReturn<T> call, T defaultReturn) { try { if (sService == null) { attemptDeadServiceRecovery(null); } - sService.notifyHceDeactivated(); + return call.call(); } catch (RemoteException e) { attemptDeadServiceRecovery(e); // Try one more time if (sService == null) { Log.e(TAG, "Failed to recover NFC Service."); - return; + return defaultReturn; } try { - sService.notifyHceDeactivated(); + return call.call(); } catch (RemoteException ee) { Log.e(TAG, "Failed to recover NFC Service."); } } + return defaultReturn; + } + + /** + * Notifies the system of a an HCE session being deactivated. + * * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void notifyHceDeactivated() { + callService(() -> sService.notifyHceDeactivated()); } /** @@ -2862,22 +2661,7 @@ public final class NfcAdapter { if (!sHasNfcWlcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.setWlcEnabled(enable); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.setWlcEnabled(enable); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.setWlcEnabled(enable), false); } /** @@ -2892,22 +2676,8 @@ public final class NfcAdapter { if (!sHasNfcWlcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.isWlcEnabled(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return false; - } - try { - return sService.isWlcEnabled(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return false; - } + return callServiceReturn(() -> sService.isWlcEnabled(), false); + } /** @@ -2986,22 +2756,8 @@ public final class NfcAdapter { if (!sHasNfcWlcFeature) { throw new UnsupportedOperationException(); } - try { - return sService.getWlcListenerDeviceInfo(); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - // Try one more time - if (sService == null) { - Log.e(TAG, "Failed to recover NFC Service."); - return null; - } - try { - return sService.getWlcListenerDeviceInfo(); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover NFC Service."); - } - return null; - } + return callServiceReturn(() -> sService.getWlcListenerDeviceInfo(), null); + } /** diff --git a/nfc/java/android/nfc/NfcAntennaInfo.java b/nfc/java/android/nfc/NfcAntennaInfo.java index b002ca21e8e3..c57b2e029cc5 100644 --- a/nfc/java/android/nfc/NfcAntennaInfo.java +++ b/nfc/java/android/nfc/NfcAntennaInfo.java @@ -64,9 +64,9 @@ public final class NfcAntennaInfo implements Parcelable { /** * Whether the device is foldable. When the device is foldable, - * the 0, 0 is considered to be bottom-left when the device is unfolded and + * the 0, 0 is considered to be top-left when the device is unfolded and * the screens are facing the user. For non-foldable devices 0, 0 - * is bottom-left when the user is facing the screen. + * is top-left when the user is facing the screen. */ public boolean isDeviceFoldable() { return mDeviceFoldable; diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index 1eff58cb80fd..f6138a63fae4 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -141,6 +141,34 @@ public final class NfcOemExtension { } } + /** + * 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() { + try { + NfcAdapter.sService.setScreenState(); + } catch (RemoteException e) { + mAdapter.attemptDeadServiceRecovery(e); + } + } + + /** + * Check if the firmware needs updating. + * + * <p>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() { + try { + NfcAdapter.sService.checkFirmware(); + } catch (RemoteException e) { + mAdapter.attemptDeadServiceRecovery(e); + } + } + private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override public void onTagConnected(boolean connected, Tag tag) throws RemoteException { diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java index 2c7d61eea777..3cf0a4dc4873 100644 --- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -108,6 +108,8 @@ public final class ApduServiceInfo implements Parcelable { private final Map<String, Boolean> mAutoTransact; + private final Map<Pattern, Boolean> mAutoTransactPatterns; + /** * Whether this service should only be started when the device is unlocked. */ @@ -179,7 +181,7 @@ public final class ApduServiceInfo implements Parcelable { this(info, onHost, description, staticAidGroups, dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid, settingsActivityName, offHost, staticOffHost, isEnabled, - new HashMap<String, Boolean>()); + new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>()); } /** @@ -189,12 +191,13 @@ public final class ApduServiceInfo implements Parcelable { List<AidGroup> staticAidGroups, List<AidGroup> dynamicAidGroups, boolean requiresUnlock, boolean requiresScreenOn, int bannerResource, int uid, String settingsActivityName, String offHost, String staticOffHost, boolean isEnabled, - HashMap<String, Boolean> autoTransact) { + Map<String, Boolean> autoTransact, Map<Pattern, Boolean> autoTransactPatterns) { this.mService = info; this.mDescription = description; this.mStaticAidGroups = new HashMap<String, AidGroup>(); this.mDynamicAidGroups = new HashMap<String, AidGroup>(); this.mAutoTransact = autoTransact; + this.mAutoTransactPatterns = autoTransactPatterns; this.mOffHostName = offHost; this.mStaticOffHostName = staticOffHost; this.mOnHost = onHost; @@ -314,6 +317,7 @@ public final class ApduServiceInfo implements Parcelable { mStaticAidGroups = new HashMap<String, AidGroup>(); mDynamicAidGroups = new HashMap<String, AidGroup>(); mAutoTransact = new HashMap<String, Boolean>(); + mAutoTransactPatterns = new HashMap<Pattern, Boolean>(); mOnHost = onHost; final int depth = parser.getDepth(); @@ -406,7 +410,29 @@ public final class ApduServiceInfo implements Parcelable { boolean autoTransact = a.getBoolean( com.android.internal.R.styleable.PollingLoopFilter_autoTransact, false); - mAutoTransact.put(plf, autoTransact); + if (!mOnHost && !autoTransact) { + Log.e(TAG, "Ignoring polling-loop-filter " + plf + + " for offhost service that isn't autoTransact"); + } else { + mAutoTransact.put(plf, autoTransact); + } + a.recycle(); + } else if (eventType == XmlPullParser.START_TAG + && "polling-loop-pattern-filter".equals(tagName) && currentGroup == null) { + final TypedArray a = res.obtainAttributes(attrs, + com.android.internal.R.styleable.PollingLoopPatternFilter); + String plf = a.getString( + com.android.internal.R.styleable.PollingLoopPatternFilter_name) + .toUpperCase(Locale.ROOT); + boolean autoTransact = a.getBoolean( + com.android.internal.R.styleable.PollingLoopFilter_autoTransact, + false); + if (!mOnHost && !autoTransact) { + Log.e(TAG, "Ignoring polling-loop-filter " + plf + + " for offhost service that isn't autoTransact"); + } else { + mAutoTransactPatterns.put(Pattern.compile(plf), autoTransact); + } a.recycle(); } } @@ -481,7 +507,30 @@ public final class ApduServiceInfo implements Parcelable { */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public boolean getShouldAutoTransact(@NonNull String plf) { - return mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false); + if (mAutoTransact.getOrDefault(plf.toUpperCase(Locale.ROOT), false)) { + return true; + } + List<Pattern> patternMatches = mAutoTransactPatterns.keySet().stream() + .filter(p -> p.matcher(plf).matches()).toList(); + if (patternMatches == null || patternMatches.size() == 0) { + return false; + } + for (Pattern patternMatch : patternMatches) { + if (mAutoTransactPatterns.get(patternMatch)) { + return true; + } + } + return false; + } + + /** + * Returns the current polling loop pattern filters for this service. + * @return List of polling loop pattern filters. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + @NonNull + public List<Pattern> getPollingLoopPatternFilters() { + return new ArrayList<>(mAutoTransactPatterns.keySet()); } /** @@ -683,13 +732,17 @@ public final class ApduServiceInfo implements Parcelable { * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this * multiple times will cause the value to be overwritten each time. - * @param pollingLoopFilter the polling loop filter to add, must be a valide hexadecimal string + * @param pollingLoopFilter the polling loop filter to add, must be a valid hexadecimal string + * @param autoTransact when true, disable observe mode when this filter matches, when false, + * matching does not change the observe mode state */ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) public void addPollingLoopFilter(@NonNull String pollingLoopFilter, boolean autoTransact) { + if (!mOnHost && !autoTransact) { + return; + } mAutoTransact.put(pollingLoopFilter, autoTransact); - } /** @@ -703,6 +756,35 @@ public final class ApduServiceInfo implements Parcelable { } /** + * Add a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will be + * delivered to {@link HostApduService#processPollingFrames(List)}. Adding a key with this + * multiple times will cause the value to be overwritten each time. + * @param pollingLoopPatternFilter the polling loop pattern filter to add, must be a valid + * regex to match a hexadecimal string + * @param autoTransact when true, disable observe mode when this filter matches, when false, + * matching does not change the observe mode state + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void addPollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter, + boolean autoTransact) { + if (!mOnHost && !autoTransact) { + return; + } + mAutoTransactPatterns.put(Pattern.compile(pollingLoopPatternFilter), autoTransact); + } + + /** + * Remove a Polling Loop Pattern Filter. Custom NFC polling frames that match this filter will + * no longer be delivered to {@link HostApduService#processPollingFrames(List)}. + * @param pollingLoopPatternFilter this polling loop filter to add. + */ + @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP) + public void removePollingLoopPatternFilter(@NonNull String pollingLoopPatternFilter) { + mAutoTransactPatterns.remove( + Pattern.compile(pollingLoopPatternFilter.toUpperCase(Locale.ROOT))); + } + + /** * Sets the off host Secure Element. * @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE. * Ref: GSMA TS.26 - NFC Handset Requirements @@ -856,6 +938,8 @@ public final class ApduServiceInfo implements Parcelable { dest.writeInt(mCategoryOtherServiceEnabled ? 1 : 0); dest.writeInt(mAutoTransact.size()); dest.writeMap(mAutoTransact); + dest.writeInt(mAutoTransactPatterns.size()); + dest.writeMap(mAutoTransactPatterns); }; @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE) @@ -889,10 +973,15 @@ public final class ApduServiceInfo implements Parcelable { new HashMap<String, Boolean>(autoTransactSize); source.readMap(autoTransact, getClass().getClassLoader(), String.class, Boolean.class); + int autoTransactPatternSize = source.readInt(); + HashMap<Pattern, Boolean> autoTransactPatterns = + new HashMap<Pattern, Boolean>(autoTransactSize); + source.readMap(autoTransactPatterns, getClass().getClassLoader(), + Pattern.class, Boolean.class); return new ApduServiceInfo(info, onHost, description, staticAidGroups, dynamicAidGroups, requiresUnlock, requiresScreenOn, bannerResource, uid, settingsActivityName, offHostName, staticOffHostName, - isEnabled, autoTransact); + isEnabled, autoTransact, autoTransactPatterns); } @Override @@ -939,6 +1028,9 @@ public final class ApduServiceInfo implements Parcelable { pw.println(" Settings Activity: " + mSettingsActivityName); pw.println(" Requires Device Unlock: " + mRequiresDeviceUnlock); pw.println(" Requires Device ScreenOn: " + mRequiresDeviceScreenOn); + pw.println(" Should Default to Observe Mode: " + mShouldDefaultToObserveMode); + pw.println(" Auto-Transact Mapping: " + mAutoTransact); + pw.println(" Auto-Transact Patterns: " + mAutoTransactPatterns); } @@ -992,6 +1084,27 @@ public final class ApduServiceInfo implements Parcelable { proto.end(token); } proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName); + proto.write(ApduServiceInfoProto.SHOULD_DEFAULT_TO_OBSERVE_MODE, + mShouldDefaultToObserveMode); + { + long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_MAPPING); + for (Map.Entry<String, Boolean> entry : mAutoTransact.entrySet()) { + proto.write(ApduServiceInfoProto.AutoTransactMapping.AID, entry.getKey()); + proto.write(ApduServiceInfoProto.AutoTransactMapping.SHOULD_AUTO_TRANSACT, + entry.getValue()); + } + proto.end(token); + } + { + long token = proto.start(ApduServiceInfoProto.AUTO_TRANSACT_PATTERNS); + for (Map.Entry<Pattern, Boolean> entry : mAutoTransactPatterns.entrySet()) { + proto.write(ApduServiceInfoProto.AutoTransactPattern.REGEXP_PATTERN, + entry.getKey().pattern()); + proto.write(ApduServiceInfoProto.AutoTransactPattern.SHOULD_AUTO_TRANSACT, + entry.getValue()); + } + proto.end(token); + } } private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?"); diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index 61d651fa5577..2fe2ce39813b 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -27,6 +27,7 @@ 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; @@ -35,6 +36,7 @@ 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; @@ -44,6 +46,7 @@ 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; /** @@ -60,6 +63,7 @@ import java.util.regex.Pattern; */ 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"; @@ -268,12 +272,16 @@ public final class CardEmulation { } /** + * <p> * 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. + * <p class="note"> + * 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 @@ -281,10 +289,16 @@ public final class CardEmulation { */ @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; - Context contextAsUser = mContext.createContextAsUser( - UserHandle.of(UserHandle.myUserId()), 0); try { preferForeground = Settings.Secure.getInt( contextAsUser.getContentResolver(), @@ -307,6 +321,11 @@ public final class CardEmulation { * every time what service they would like to use in this category. * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked * to pick a service if there is a conflict. + * + * <p class="note"> + * 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 */ @@ -364,9 +383,9 @@ public final class CardEmulation { * 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, then observe mode will also be disabled. if this service is currently preferred or - * there are no other services registered for this filter. + * 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 @@ -401,6 +420,128 @@ public final class CardEmulation { } /** + * 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. * @@ -780,6 +921,13 @@ public final class CardEmulation { /** * Retrieves the route destination for the preferred payment service. * + * <p class="note"> + * 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. @@ -835,6 +983,13 @@ public final class CardEmulation { /** * Returns a user-visible description of the preferred payment service. * + * <p class="note"> + * 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) @@ -998,6 +1153,23 @@ public final class CardEmulation { } /** + * 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: * <ul> * <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars) diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index f3ba2d0098ce..c3c74a6fd265 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -326,15 +326,12 @@ public abstract class HostApduService extends Service { } break; case MSG_POLLING_LOOP: - ArrayList<Bundle> frames = - msg.getData().getParcelableArrayList(KEY_POLLING_LOOP_FRAMES_BUNDLE, - Bundle.class); - ArrayList<PollingFrame> pollingFrames = - new ArrayList<PollingFrame>(frames.size()); - for (Bundle frame : frames) { - pollingFrames.add(new PollingFrame(frame)); + if (android.nfc.Flags.nfcReadPollingLoop()) { + ArrayList<PollingFrame> pollingFrames = + msg.getData().getParcelableArrayList( + KEY_POLLING_LOOP_FRAMES_BUNDLE, PollingFrame.class); + processPollingFrames(pollingFrames); } - processPollingFrames(pollingFrames); break; default: super.handleMessage(msg); diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.aidl b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl new file mode 100644 index 000000000000..8e09f8baaff2 --- /dev/null +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.aidl @@ -0,0 +1,19 @@ +/* + * 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.cardemulation; + +parcelable PollingFrame;
\ No newline at end of file diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index 994f4ae1c2e3..5dcc84ccf8b9 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -32,19 +32,26 @@ import java.util.List; /** * Polling Frames represent data about individual frames of an NFC polling loop. These frames will - * be deliverd to subclasses of {@link HostApduService} that have registered filters with - * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a - * given frame in a loop and will be delivered through calls to + * be delivered to subclasses of {@link HostApduService} that have registered filters with + * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String, boolean)} that + * match a given frame in a loop and will be delivered through calls to * {@link HostApduService#processPollingFrames(List)}. */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) -public final class PollingFrame implements Parcelable{ +public final class PollingFrame implements Parcelable { /** * @hide */ - @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, value = { POLLING_LOOP_TYPE_A, POLLING_LOOP_TYPE_B, - POLLING_LOOP_TYPE_F, POLLING_LOOP_TYPE_OFF, POLLING_LOOP_TYPE_ON }) + @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, + value = { + POLLING_LOOP_TYPE_A, + POLLING_LOOP_TYPE_B, + POLLING_LOOP_TYPE_F, + POLLING_LOOP_TYPE_OFF, + POLLING_LOOP_TYPE_ON, + POLLING_LOOP_TYPE_UNKNOWN + }) @Retention(RetentionPolicy.SOURCE) @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public @interface PollingFrameType {} @@ -100,45 +107,46 @@ public final class PollingFrame implements Parcelable{ /** * KEY_POLLING_LOOP_TYPE is the Bundle key for the type of * polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE"; + private static final String KEY_POLLING_LOOP_TYPE = "android.nfc.cardemulation.TYPE"; /** * KEY_POLLING_LOOP_DATA is the Bundle key for the raw data of captured from * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA"; + private static final String KEY_POLLING_LOOP_DATA = "android.nfc.cardemulation.DATA"; /** * KEY_POLLING_LOOP_GAIN is the Bundle key for the field strength of * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide - */ + */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN"; + private static final String KEY_POLLING_LOOP_GAIN = "android.nfc.cardemulation.GAIN"; /** * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for the timestamp of * the polling loop frame in the Bundle included in MSG_POLLING_LOOP. - * - * @hide - */ + */ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) - public static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP"; + private static final String KEY_POLLING_LOOP_TIMESTAMP = "android.nfc.cardemulation.TIMESTAMP"; + + /** + * KEY_POLLING_LOOP_TIMESTAMP is the Bundle key for whether this polling frame triggered + * autoTransact in the Bundle included in MSG_POLLING_LOOP. + */ + @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) + private static final String KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT = + "android.nfc.cardemulation.TRIGGERED_AUTOTRANSACT"; @PollingFrameType private final int mType; private final byte[] mData; private final int mGain; - private final int mTimestamp; + private final long mTimestamp; + private boolean mTriggeredAutoTransact; public static final @NonNull Parcelable.Creator<PollingFrame> CREATOR = new Parcelable.Creator<>() { @@ -153,31 +161,46 @@ public final class PollingFrame implements Parcelable{ } }; - PollingFrame(Bundle frame) { + private PollingFrame(Bundle frame) { mType = frame.getInt(KEY_POLLING_LOOP_TYPE); byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA); mData = (data == null) ? new byte[0] : data; - mGain = frame.getByte(KEY_POLLING_LOOP_GAIN); - mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP); + mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1); + mTimestamp = frame.getLong(KEY_POLLING_LOOP_TIMESTAMP); + mTriggeredAutoTransact = frame.containsKey(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT) + && frame.getBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT); } + /** + * Constructor for Polling Frames. + * + * @param type the type of the frame + * @param data a byte array of the data contained in the frame + * @param gain the vendor-specific gain of the field + * @param timestampMicros the timestamp in microseconds + * @param triggeredAutoTransact whether or not this frame triggered the device to start a + * transaction automatically + * + * @hide + */ public PollingFrame(@PollingFrameType int type, @Nullable byte[] data, - int gain, int timestamp) { + int gain, long timestampMicros, boolean triggeredAutoTransact) { mType = type; mData = data == null ? new byte[0] : data; mGain = gain; - mTimestamp = timestamp; + mTimestamp = timestampMicros; + mTriggeredAutoTransact = triggeredAutoTransact; } /** * Returns the type of frame for this polling loop frame. * The possible return values are: * <ul> - * <li>{@link POLLING_LOOP_TYPE_ON}</li> - * <li>{@link POLLING_LOOP_TYPE_OFF}</li> - * <li>{@link POLLING_LOOP_TYPE_A}</li> - * <li>{@link POLLING_LOOP_TYPE_B}</li> - * <li>{@link POLLING_LOOP_TYPE_F}</li> + * <li>{@link #POLLING_LOOP_TYPE_ON}</li> + * <li>{@link #POLLING_LOOP_TYPE_OFF}</li> + * <li>{@link #POLLING_LOOP_TYPE_A}</li> + * <li>{@link #POLLING_LOOP_TYPE_B}</li> + * <li>{@link #POLLING_LOOP_TYPE_F}</li> * </ul> */ public @PollingFrameType int getType() { @@ -194,21 +217,37 @@ public final class PollingFrame implements Parcelable{ /** * Returns the gain representing the field strength of the NFC field when this polling loop * frame was observed. + * @return the gain or -1 if there is no gain measurement associated with this frame. */ - public int getGain() { + public int getVendorSpecificGain() { return mGain; } /** - * Returns the timestamp of when the polling loop frame was observed in milliseconds. These - * timestamps are relative and not absolute and should only be used for comparing the timing of - * frames relative to each other. - * @return the timestamp in milliseconds + * Returns the timestamp of when the polling loop frame was observed, in microseconds. These + * timestamps are relative and should only be used for comparing the timing of frames relative + * to each other. + * @return the timestamp in microseconds */ - public int getTimestamp() { + public long getTimestamp() { return mTimestamp; } + /** + * @hide + */ + public void setTriggeredAutoTransact(boolean triggeredAutoTransact) { + mTriggeredAutoTransact = triggeredAutoTransact; + } + + /** + * Returns whether this frame triggered the device to automatically disable observe mode and + * allow one transaction. + */ + public boolean getTriggeredAutoTransact() { + return mTriggeredAutoTransact; + } + @Override public int describeContents() { return 0; @@ -220,24 +259,25 @@ public final class PollingFrame implements Parcelable{ } /** - * - * @hide * @return a Bundle representing this frame */ - public Bundle toBundle() { + private Bundle toBundle() { Bundle frame = new Bundle(); frame.putInt(KEY_POLLING_LOOP_TYPE, getType()); - frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain()); + if (getVendorSpecificGain() != -1) { + frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain()); + } frame.putByteArray(KEY_POLLING_LOOP_DATA, getData()); - frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp()); + frame.putLong(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp()); + frame.putBoolean(KEY_POLLING_LOOP_TRIGGERED_AUTOTRANSACT, getTriggeredAutoTransact()); return frame; } @Override public String toString() { return "PollingFrame { Type: " + (char) getType() - + ", gain: " + getGain() - + ", timestamp: " + Integer.toUnsignedString(getTimestamp()) + + ", gain: " + getVendorSpecificGain() + + ", timestamp: " + Long.toUnsignedString(getTimestamp()) + ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }"; } } diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 6841d2ba96ad..45036a5f214b 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -92,3 +92,28 @@ flag { description: "Enable NFC OEM extension support" bug: "331206243" } + +flag { + name: "nfc_state_change" + is_exported: true + namespace: "nfc" + description: "Enable nfc state change API" + bug: "319934052" +} + +flag { + name: "nfc_set_default_disc_tech" + is_exported: true + namespace: "nfc" + description: "Flag for NFC set default disc tech API" + bug: "321311407" +} + +flag { + name: "nfc_persist_log" + is_exported: true + namespace: "nfc" + description: "Enable NFC persistent log support" + bug: "321310044" +} + diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index ee755dd395d8..8c1eab90bba5 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -156,7 +156,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, UserManager.DISALLOW_SIM_GLOBALLY, UserManager.DISALLOW_ASSIST_CONTENT, - UserManager.DISALLOW_THREAD_NETWORK + UserManager.DISALLOW_THREAD_NETWORK, + UserManager.DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO }); public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet( @@ -208,7 +209,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, - UserManager.DISALLOW_THREAD_NETWORK + UserManager.DISALLOW_THREAD_NETWORK, + UserManager.DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO ); /** @@ -255,8 +257,9 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CELLULAR_2G, UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, UserManager.DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO, - UserManager.DISALLOW_THREAD_NETWORK - ); + UserManager.DISALLOW_THREAD_NETWORK, + UserManager.DISALLOW_CHANGE_NEAR_FIELD_COMMUNICATION_RADIO + ); /** * Special user restrictions that profile owner of an organization-owned managed profile can |