Merge "Code cleanup: removed support of P2P in NfcDiscoveryParameters" into main am: 7176a36187 am: 96c409471a
Original change: https://android-review.googlesource.com/c/platform/packages/apps/Nfc/+/3047814
Change-Id: Id2889fd45193be29ce7802d7e902609a38c7a5e9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 830da16..60489f4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,12 +1,13 @@
package {
+ default_team: "trendy_team_fwk_nfc",
default_applicable_licenses: ["Android-Apache-2.0"],
}
genrule {
name: "statslog-Nfc-java-gen",
tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module nfc --javaPackage com.android.nfc"
- + " --javaClass NfcStatsLog",
+ cmd: "$(location stats-log-api-gen) --java $(out) --module nfc --javaPackage com.android.nfc" +
+ " --javaClass NfcStatsLog",
out: ["com/android/nfc/NfcStatsLog.java"],
}
@@ -23,37 +24,12 @@
"//apex_available:platform",
"com.android.nfcservices",
],
- min_sdk_version: "VanillaIceCream",
+ min_sdk_version: "current",
sdk_version: "module_current",
}
-// NCI Configuration
-android_app {
- name: "NfcNci",
- srcs: [
- "src/**/*.java",
- "nci/**/*.java",
- ":framework-nfc-javastream-protos",
- // TODO(b/263565193): Temp hack until NFC APK can link against module_current.
- ":framework-nfc-updatable-sources",
- ":statslog-Nfc-java-gen",
- ],
- platform_apis: true,
- privileged: true,
- certificate: "platform",
- // TODO(b/263565193): Change when NFC APK can link against module_current.
- // min_sdk_version: "VanillaIceCream",
- // sdk_version: "module_current",
- jni_libs: ["libnfc_nci_jni"],
- libs: [
- "framework-annotations-lib",
- "framework-bluetooth",
- "framework-configinfrastructure",
- "framework-nfc.impl",
- "framework-statsd.stubs.module_lib",
- "framework-wifi",
- "unsupportedappusage"
- ],
+java_defaults {
+ name: "NfcNciDefaults",
static_libs: [
"android.se.omapi-V1-java",
"androidx.annotation_annotation",
@@ -61,24 +37,85 @@
"bluetooth-protos-nfc-enums-java-gen",
"com.google.android.material_material",
"modules-utils-fastxmlserializer",
+ "modules-utils-shell-command-handler",
"PlatformProperties",
+ "nfc-event-log-proto",
"nfc_flags_lib",
],
+ privileged: true,
optimize: {
proguard_flags_files: ["proguard.flags"],
},
- apex_available: [
- "//apex_available:platform",
- "com.android.nfcservices"
- ],
jarjar_rules: "jarjar-rules.txt",
privapp_allowlist: ":privapp_allowlist_com.android.nfc.xml",
}
+// NCI Configuration used without NFC apex
+// This version compiles against platform.
+android_app {
+ name: "NfcNci",
+ defaults: ["NfcNciDefaults"],
+ certificate: "platform",
+ sdk_version: "core_platform",
+ srcs: [
+ ":nfc-sources",
+ "shim_src/non_apex/**/*.java",
+ ],
+ libs: [
+ // order matters: classes in framework-nfc are resolved before framework, meaning
+ // @hide APIs in framework-nfc are resolved before @SystemApi stubs in framework
+ "framework-nfc.impl",
+ "framework",
+
+ // if sdk_version="" this gets automatically included, but here we need to add manually.
+ "framework-res",
+ ],
+ jni_libs: ["libnfc_nci_jni"],
+}
+
+// NCI Configuration embedded in NFC apex.
+// This version compiles against SDK API's.
+android_app {
+ name: "NfcNciApex",
+ defaults: ["NfcNciDefaults"],
+ min_sdk_version: "current",
+ sdk_version: "module_current",
+ certificate: "nfc",
+ srcs: [
+ ":nfc-sources",
+ "shim_src/apex/**/*.java",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "framework-bluetooth",
+ "framework-configinfrastructure",
+ "framework-nfc.impl",
+ "framework-permission-s",
+ "framework-permission",
+ "framework-statsd.stubs.module_lib",
+ "framework-wifi",
+ "android.nfc.flags-aconfig-java",
+ "android.permission.flags-aconfig-java",
+ "android.service.chooser.flags-aconfig-java",
+ "unsupportedappusage",
+ ],
+ // prevent NfcNciApex from using product-specific resources
+ aaptflags: ["--product default"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
+}
+
filegroup {
name: "nfc-sources",
srcs: [
"src/**/*.java",
+ "nci/**/*.java",
+ ":framework-nfc-javastream-protos",
":statslog-Nfc-java-gen",
],
visibility: [
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4470f3f..f5a72b5 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -65,6 +65,12 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE" />
+ <uses-permission android:name="android.permission.SHOW_CUSTOMIZED_RESOLVER" />
+ <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+ <uses-permission android:name="android.permission.DUMP"/>
+ <uses-permission android:name="android.permission.QUERY_CLONED_APPS"/>
+ <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<application android:name=".NfcApplication"
android:icon="@drawable/icon"
@@ -72,6 +78,7 @@
android:theme="@android:style/Theme.Material.Light"
android:persistent="true"
android:persistentWhenFeatureAvailable="android.hardware.nfc.any"
+ android:restoreAnyVersion="true"
android:backupAgent="com.android.nfc.NfcBackupAgent"
android:killAfterRestore="false"
android:usesCleartextTraffic="false"
@@ -132,6 +139,10 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:noHistory="true"
/>
+ <activity android:name=".NfcEnableAllowlistActivity"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar"
+ android:noHistory="true"
+ />
<receiver android:name=".NfcBootCompletedReceiver"
android:exported="true">
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 7fb1636..0ccc9cb 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,3 +1,6 @@
+[Hook Scripts]
+hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
+
[Options]
ignore_merged_commits = true
diff --git a/apex/Android.bp b/apex/Android.bp
index 53523a8..0ffbd56 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_fwk_nfc",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -27,18 +28,36 @@
certificate: "com.android.nfcservices",
}
-apex {
+soong_config_module_type {
+ name: "custom_apex",
+ module_type: "apex",
+ config_namespace: "bootclasspath",
+ bool_variables: [
+ "nfc_apex_bootclasspath_fragment",
+ ],
+ properties: [
+ "bootclasspath_fragments",
+ ],
+}
+
+custom_apex {
name: "com.android.nfcservices",
manifest: "manifest.json",
- // apps: ["NfcNci"],
+ apps: ["NfcNciApex"],
multilib: {
- first: {
- // Extractor process runs only with the primary ABI.
- jni_libs: [
- "libnfc_nci_jni",
- ],
- },
+ first: {
+ // Extractor process runs only with the primary ABI.
+ jni_libs: [
+ "libnfc_nci_jni",
+ ],
+ },
},
+ soong_config_variables: {
+ nfc_apex_bootclasspath_fragment: {
+ bootclasspath_fragments: ["com.android.nfcservices-bootclasspath-fragment"],
+ },
+ },
+
required: [
// Provide a default libnfc-nci.conf in /system/etc for devices that
// does not ship one in /product
@@ -48,3 +67,77 @@
certificate: ":com.android.nfcservices.certificate",
updatable: false,
}
+
+soong_config_module_type {
+ name: "custom_bootclasspath_fragment",
+ module_type: "bootclasspath_fragment",
+ config_namespace: "bootclasspath",
+ bool_variables: [
+ "nfc_apex_bootclasspath_fragment",
+ ],
+ properties: [
+ "enabled",
+ ],
+}
+
+// Encapsulate the contributions made by the com.android.nfc to the bootclasspath.
+custom_bootclasspath_fragment {
+ name: "com.android.nfcservices-bootclasspath-fragment",
+ contents: ["framework-nfc"],
+ apex_available: ["com.android.nfcservices"],
+ // This is disabled for now since the build system does not allow for adding a bootclasspath
+ // fragment and conditionally adding it to PRODUCT_APEX_BOOT_JARS.
+ // When `RELEASE_PACKAGE_NFC_STACK` is set to `com.android.nfcservices`, this needs to be
+ // set to true.
+ enabled: false,
+ soong_config_variables: {
+ nfc_apex_bootclasspath_fragment: {
+ enabled: true,
+ },
+ },
+
+ // The bootclasspath_fragments that provide APIs on which this depends.
+ fragments: [
+ // Needed to access core java APIs.
+ {
+ apex: "com.android.art",
+ module: "art-bootclasspath-fragment",
+ },
+ ],
+
+ // Additional stubs libraries that this fragment's contents use which are
+ // not provided by another bootclasspath_fragment.
+ additional_stubs: [
+ // Needed to access platform APIs.
+ "android-non-updatable",
+ ],
+ hidden_api: {
+ // Additional hidden API flag files to override the defaults. This must only be
+ // modified by the Soong or platform compat team.
+ max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o.txt"],
+ max_target_r_low_priority: ["hiddenapi/hiddenapi-max-target-r-loprio.txt"],
+
+ // The following packages contain classes from other modules on the
+ // bootclasspath. That means that the hidden API flags for this module
+ // has to explicitly list every single class this module provides in
+ // that package to differentiate them from the classes provided by other
+ // modules. That can include private classes that are not part of the
+ // API.
+ split_packages: [
+ "android.nfc",
+ "android.nfc.cardemulation",
+ ],
+
+ // The following packages and all their subpackages currently only
+ // contain classes from this bootclasspath_fragment. Listing a package
+ // here won't prevent other bootclasspath modules from adding classes in
+ // any of those packages but it will prevent them from adding those
+ // classes into an API surface, e.g. public, system, etc.. Doing so will
+ // result in a build failure due to inconsistent flags.
+ package_prefixes: [
+ "android.nfc.dta",
+ "android.nfc.tech",
+ "com.android.nfc",
+ ],
+ },
+}
diff --git a/apex/hiddenapi/OWNERS b/apex/hiddenapi/OWNERS
new file mode 100644
index 0000000..ac8a2b6
--- /dev/null
+++ b/apex/hiddenapi/OWNERS
@@ -0,0 +1,5 @@
+# soong-team@ as the hiddenapi files are tightly coupled with Soong
+file:platform/build/soong:/OWNERS
+
+# compat-team@ for changes to hiddenapi files
+file:tools/platform-compat:/OWNERS
diff --git a/apex/hiddenapi/hiddenapi-max-target-o.txt b/apex/hiddenapi/hiddenapi-max-target-o.txt
new file mode 100644
index 0000000..9012923
--- /dev/null
+++ b/apex/hiddenapi/hiddenapi-max-target-o.txt
@@ -0,0 +1,643 @@
+Landroid/nfc/ApduList;-><init>()V
+Landroid/nfc/ApduList;-><init>(Landroid/os/Parcel;)V
+Landroid/nfc/ApduList;->add([B)V
+Landroid/nfc/ApduList;->commands:Ljava/util/ArrayList;
+Landroid/nfc/ApduList;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/nfc/ApduList;->get()Ljava/util/List;
+Landroid/nfc/BeamShareData;-><init>(Landroid/nfc/NdefMessage;[Landroid/net/Uri;Landroid/os/UserHandle;I)V
+Landroid/nfc/BeamShareData;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/nfc/BeamShareData;->flags:I
+Landroid/nfc/BeamShareData;->ndefMessage:Landroid/nfc/NdefMessage;
+Landroid/nfc/BeamShareData;->uris:[Landroid/net/Uri;
+Landroid/nfc/BeamShareData;->userHandle:Landroid/os/UserHandle;
+Landroid/nfc/cardemulation/AidGroup;-><init>(Ljava/util/List;Ljava/lang/String;)V
+Landroid/nfc/cardemulation/AidGroup;->isValidCategory(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/AidGroup;->MAX_NUM_AIDS:I
+Landroid/nfc/cardemulation/AidGroup;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/ApduServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
+Landroid/nfc/cardemulation/ApduServiceInfo;->getAidGroups()Ljava/util/ArrayList;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getAids()Ljava/util/List;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getCategoryForAid(Ljava/lang/String;)Ljava/lang/String;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getComponent()Landroid/content/ComponentName;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getDynamicAidGroupForCategory(Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getPrefixAids()Ljava/util/List;
+Landroid/nfc/cardemulation/ApduServiceInfo;->getSubsetAids()Ljava/util/List;
+Landroid/nfc/cardemulation/ApduServiceInfo;->hasCategory(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/ApduServiceInfo;->loadAppLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
+Landroid/nfc/cardemulation/ApduServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
+Landroid/nfc/cardemulation/ApduServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
+Landroid/nfc/cardemulation/ApduServiceInfo;->mBannerResourceId:I
+Landroid/nfc/cardemulation/ApduServiceInfo;->mDescription:Ljava/lang/String;
+Landroid/nfc/cardemulation/ApduServiceInfo;->mOnHost:Z
+Landroid/nfc/cardemulation/ApduServiceInfo;->mRequiresDeviceUnlock:Z
+Landroid/nfc/cardemulation/ApduServiceInfo;->mSettingsActivityName:Ljava/lang/String;
+Landroid/nfc/cardemulation/ApduServiceInfo;->mUid:I
+Landroid/nfc/cardemulation/ApduServiceInfo;->removeDynamicAidGroupForCategory(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/ApduServiceInfo;->setOrReplaceDynamicAidGroup(Landroid/nfc/cardemulation/AidGroup;)V
+Landroid/nfc/cardemulation/ApduServiceInfo;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/CardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcCardEmulation;)V
+Landroid/nfc/cardemulation/CardEmulation;->getServices(Ljava/lang/String;)Ljava/util/List;
+Landroid/nfc/cardemulation/CardEmulation;->isValidAid(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/CardEmulation;->mContext:Landroid/content/Context;
+Landroid/nfc/cardemulation/CardEmulation;->recoverService()V
+Landroid/nfc/cardemulation/CardEmulation;->sCardEmus:Ljava/util/HashMap;
+Landroid/nfc/cardemulation/CardEmulation;->setDefaultForNextTap(Landroid/content/ComponentName;)Z
+Landroid/nfc/cardemulation/CardEmulation;->setDefaultServiceForCategory(Landroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/CardEmulation;->sIsInitialized:Z
+Landroid/nfc/cardemulation/CardEmulation;->sService:Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/cardemulation/CardEmulation;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/HostApduService;->KEY_DATA:Ljava/lang/String;
+Landroid/nfc/cardemulation/HostApduService;->mMessenger:Landroid/os/Messenger;
+Landroid/nfc/cardemulation/HostApduService;->mNfcService:Landroid/os/Messenger;
+Landroid/nfc/cardemulation/HostApduService;->MSG_COMMAND_APDU:I
+Landroid/nfc/cardemulation/HostApduService;->MSG_DEACTIVATED:I
+Landroid/nfc/cardemulation/HostApduService;->MSG_RESPONSE_APDU:I
+Landroid/nfc/cardemulation/HostApduService;->MSG_UNHANDLED:I
+Landroid/nfc/cardemulation/HostApduService;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/HostNfcFService;->KEY_DATA:Ljava/lang/String;
+Landroid/nfc/cardemulation/HostNfcFService;->KEY_MESSENGER:Ljava/lang/String;
+Landroid/nfc/cardemulation/HostNfcFService;->mMessenger:Landroid/os/Messenger;
+Landroid/nfc/cardemulation/HostNfcFService;->mNfcService:Landroid/os/Messenger;
+Landroid/nfc/cardemulation/HostNfcFService;->MSG_COMMAND_PACKET:I
+Landroid/nfc/cardemulation/HostNfcFService;->MSG_DEACTIVATED:I
+Landroid/nfc/cardemulation/HostNfcFService;->MSG_RESPONSE_PACKET:I
+Landroid/nfc/cardemulation/HostNfcFService;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFCardEmulation;-><init>(Landroid/content/Context;Landroid/nfc/INfcFCardEmulation;)V
+Landroid/nfc/cardemulation/NfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
+Landroid/nfc/cardemulation/NfcFCardEmulation;->getNfcFServices()Ljava/util/List;
+Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidNfcid2(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/NfcFCardEmulation;->isValidSystemCode(Ljava/lang/String;)Z
+Landroid/nfc/cardemulation/NfcFCardEmulation;->mContext:Landroid/content/Context;
+Landroid/nfc/cardemulation/NfcFCardEmulation;->recoverService()V
+Landroid/nfc/cardemulation/NfcFCardEmulation;->sCardEmus:Ljava/util/HashMap;
+Landroid/nfc/cardemulation/NfcFCardEmulation;->sIsInitialized:Z
+Landroid/nfc/cardemulation/NfcFCardEmulation;->sService:Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/cardemulation/NfcFCardEmulation;->TAG:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/PackageManager;Landroid/content/pm/ResolveInfo;)V
+Landroid/nfc/cardemulation/NfcFServiceInfo;-><init>(Landroid/content/pm/ResolveInfo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V
+Landroid/nfc/cardemulation/NfcFServiceInfo;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->DEFAULT_T3T_PMM:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->dump(Ljava/io/FileDescriptor;Ljava/io/PrintWriter;[Ljava/lang/String;)V
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getComponent()Landroid/content/ComponentName;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getDescription()Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getNfcid2()Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getSystemCode()Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getT3tPmm()Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->getUid()I
+Landroid/nfc/cardemulation/NfcFServiceInfo;->loadIcon(Landroid/content/pm/PackageManager;)Landroid/graphics/drawable/Drawable;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->loadLabel(Landroid/content/pm/PackageManager;)Ljava/lang/CharSequence;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mDescription:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicNfcid2:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mDynamicSystemCode:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mNfcid2:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mService:Landroid/content/pm/ResolveInfo;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mSystemCode:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mT3tPmm:Ljava/lang/String;
+Landroid/nfc/cardemulation/NfcFServiceInfo;->mUid:I
+Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicNfcid2(Ljava/lang/String;)V
+Landroid/nfc/cardemulation/NfcFServiceInfo;->setOrReplaceDynamicSystemCode(Ljava/lang/String;)V
+Landroid/nfc/cardemulation/NfcFServiceInfo;->TAG:Ljava/lang/String;
+Landroid/nfc/ErrorCodes;-><init>()V
+Landroid/nfc/ErrorCodes;->asString(I)Ljava/lang/String;
+Landroid/nfc/ErrorCodes;->ERROR_BUFFER_TO_SMALL:I
+Landroid/nfc/ErrorCodes;->ERROR_BUSY:I
+Landroid/nfc/ErrorCodes;->ERROR_CANCELLED:I
+Landroid/nfc/ErrorCodes;->ERROR_CONNECT:I
+Landroid/nfc/ErrorCodes;->ERROR_DISCONNECT:I
+Landroid/nfc/ErrorCodes;->ERROR_INSUFFICIENT_RESOURCES:I
+Landroid/nfc/ErrorCodes;->ERROR_INVALID_PARAM:I
+Landroid/nfc/ErrorCodes;->ERROR_IO:I
+Landroid/nfc/ErrorCodes;->ERROR_NFC_ON:I
+Landroid/nfc/ErrorCodes;->ERROR_NOT_INITIALIZED:I
+Landroid/nfc/ErrorCodes;->ERROR_NOT_SUPPORTED:I
+Landroid/nfc/ErrorCodes;->ERROR_NO_SE_CONNECTED:I
+Landroid/nfc/ErrorCodes;->ERROR_READ:I
+Landroid/nfc/ErrorCodes;->ERROR_SAP_USED:I
+Landroid/nfc/ErrorCodes;->ERROR_SERVICE_NAME_USED:I
+Landroid/nfc/ErrorCodes;->ERROR_SE_ALREADY_SELECTED:I
+Landroid/nfc/ErrorCodes;->ERROR_SE_CONNECTED:I
+Landroid/nfc/ErrorCodes;->ERROR_SOCKET_CREATION:I
+Landroid/nfc/ErrorCodes;->ERROR_SOCKET_NOT_CONNECTED:I
+Landroid/nfc/ErrorCodes;->ERROR_SOCKET_OPTIONS:I
+Landroid/nfc/ErrorCodes;->ERROR_TIMEOUT:I
+Landroid/nfc/ErrorCodes;->ERROR_WRITE:I
+Landroid/nfc/ErrorCodes;->SUCCESS:I
+Landroid/nfc/IAppCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/IAppCallback$Stub$Proxy;->createBeamShareData(B)Landroid/nfc/BeamShareData;
+Landroid/nfc/IAppCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/IAppCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/IAppCallback$Stub$Proxy;->onNdefPushComplete(B)V
+Landroid/nfc/IAppCallback$Stub$Proxy;->onTagDiscovered(Landroid/nfc/Tag;)V
+Landroid/nfc/IAppCallback$Stub;-><init>()V
+Landroid/nfc/IAppCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/IAppCallback;
+Landroid/nfc/IAppCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/IAppCallback$Stub;->TRANSACTION_createBeamShareData:I
+Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onNdefPushComplete:I
+Landroid/nfc/IAppCallback$Stub;->TRANSACTION_onTagDiscovered:I
+Landroid/nfc/IAppCallback;->createBeamShareData(B)Landroid/nfc/BeamShareData;
+Landroid/nfc/IAppCallback;->onNdefPushComplete(B)V
+Landroid/nfc/IAppCallback;->onTagDiscovered(Landroid/nfc/Tag;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->disable(Z)Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->disableNdefPush()Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->dispatch(Landroid/nfc/Tag;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->enable()Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->enableNdefPush()Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getNfcTagInterface()Landroid/nfc/INfcTag;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->getState()I
+Landroid/nfc/INfcAdapter$Stub$Proxy;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeam()V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->isNdefPushEnabled()Z
+Landroid/nfc/INfcAdapter$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcAdapter$Stub$Proxy;->pausePolling(I)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->resumePolling()V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->setAppCallback(Landroid/nfc/IAppCallback;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->setP2pModes(II)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
+Landroid/nfc/INfcAdapter$Stub$Proxy;->verifyNfcPermission()V
+Landroid/nfc/INfcAdapter$Stub;-><init>()V
+Landroid/nfc/INfcAdapter$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapter;
+Landroid/nfc/INfcAdapter$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_addNfcUnlockHandler:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disable:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_disableNdefPush:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_dispatch:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enableNdefPush:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcAdapterExtrasInterface:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcCardEmulationInterface:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcDtaInterface:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcFCardEmulationInterface:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getNfcTagInterface:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_getState:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_ignore:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeam:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_invokeBeamInternal:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_isNdefPushEnabled:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_pausePolling:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_removeNfcUnlockHandler:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_resumePolling:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setAppCallback:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setForegroundDispatch:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setP2pModes:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_setReaderMode:I
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_verifyNfcPermission:I
+Landroid/nfc/INfcAdapter;->addNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;[I)V
+Landroid/nfc/INfcAdapter;->disable(Z)Z
+Landroid/nfc/INfcAdapter;->disableNdefPush()Z
+Landroid/nfc/INfcAdapter;->dispatch(Landroid/nfc/Tag;)V
+Landroid/nfc/INfcAdapter;->enable()Z
+Landroid/nfc/INfcAdapter;->enableNdefPush()Z
+Landroid/nfc/INfcAdapter;->getNfcAdapterExtrasInterface(Ljava/lang/String;)Landroid/nfc/INfcAdapterExtras;
+Landroid/nfc/INfcAdapter;->getNfcCardEmulationInterface()Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/INfcAdapter;->getNfcDtaInterface(Ljava/lang/String;)Landroid/nfc/INfcDta;
+Landroid/nfc/INfcAdapter;->getNfcFCardEmulationInterface()Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/INfcAdapter;->getNfcTagInterface()Landroid/nfc/INfcTag;
+Landroid/nfc/INfcAdapter;->getState()I
+Landroid/nfc/INfcAdapter;->ignore(IILandroid/nfc/ITagRemovedCallback;)Z
+Landroid/nfc/INfcAdapter;->invokeBeam()V
+Landroid/nfc/INfcAdapter;->invokeBeamInternal(Landroid/nfc/BeamShareData;)V
+Landroid/nfc/INfcAdapter;->isNdefPushEnabled()Z
+Landroid/nfc/INfcAdapter;->pausePolling(I)V
+Landroid/nfc/INfcAdapter;->removeNfcUnlockHandler(Landroid/nfc/INfcUnlockHandler;)V
+Landroid/nfc/INfcAdapter;->resumePolling()V
+Landroid/nfc/INfcAdapter;->setAppCallback(Landroid/nfc/IAppCallback;)V
+Landroid/nfc/INfcAdapter;->setForegroundDispatch(Landroid/app/PendingIntent;[Landroid/content/IntentFilter;Landroid/nfc/TechListParcel;)V
+Landroid/nfc/INfcAdapter;->setP2pModes(II)V
+Landroid/nfc/INfcAdapter;->setReaderMode(Landroid/os/IBinder;Landroid/nfc/IAppCallback;ILandroid/os/Bundle;)V
+Landroid/nfc/INfcAdapter;->verifyNfcPermission()V
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->authenticate(Ljava/lang/String;[B)V
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->close(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getCardEmulationRoute(Ljava/lang/String;)I
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getDriverName(Ljava/lang/String;)Ljava/lang/String;
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->open(Ljava/lang/String;Landroid/os/IBinder;)Landroid/os/Bundle;
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->setCardEmulationRoute(Ljava/lang/String;I)V
+Landroid/nfc/INfcAdapterExtras$Stub$Proxy;->transceive(Ljava/lang/String;[B)Landroid/os/Bundle;
+Landroid/nfc/INfcAdapterExtras$Stub;-><init>()V
+Landroid/nfc/INfcAdapterExtras$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcAdapterExtras;
+Landroid/nfc/INfcAdapterExtras$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_authenticate:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_close:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getCardEmulationRoute:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_getDriverName:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_open:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_setCardEmulationRoute:I
+Landroid/nfc/INfcAdapterExtras$Stub;->TRANSACTION_transceive:I
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->getServices(ILjava/lang/String;)Ljava/util/List;
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->setPreferredService(Landroid/content/ComponentName;)Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->supportsAidPrefixRegistration()Z
+Landroid/nfc/INfcCardEmulation$Stub$Proxy;->unsetPreferredService()Z
+Landroid/nfc/INfcCardEmulation$Stub;-><init>()V
+Landroid/nfc/INfcCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/INfcCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getAidGroupForService:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_getServices:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForAid:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_isDefaultServiceForCategory:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_registerAidGroupForService:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_removeAidGroupForService:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultForNextTap:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setDefaultServiceForCategory:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_setPreferredService:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_supportsAidPrefixRegistration:I
+Landroid/nfc/INfcCardEmulation$Stub;->TRANSACTION_unsetPreferredService:I
+Landroid/nfc/INfcCardEmulation;->getAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Landroid/nfc/cardemulation/AidGroup;
+Landroid/nfc/INfcCardEmulation;->getServices(ILjava/lang/String;)Ljava/util/List;
+Landroid/nfc/INfcCardEmulation;->isDefaultServiceForAid(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation;->isDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation;->registerAidGroupForService(ILandroid/content/ComponentName;Landroid/nfc/cardemulation/AidGroup;)Z
+Landroid/nfc/INfcCardEmulation;->removeAidGroupForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation;->setDefaultForNextTap(ILandroid/content/ComponentName;)Z
+Landroid/nfc/INfcCardEmulation;->setDefaultServiceForCategory(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcCardEmulation;->setPreferredService(Landroid/content/ComponentName;)Z
+Landroid/nfc/INfcCardEmulation;->supportsAidPrefixRegistration()Z
+Landroid/nfc/INfcCardEmulation;->unsetPreferredService()Z
+Landroid/nfc/INfcDta$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcDta$Stub$Proxy;->disableClient()V
+Landroid/nfc/INfcDta$Stub$Proxy;->disableDta()V
+Landroid/nfc/INfcDta$Stub$Proxy;->disableServer()V
+Landroid/nfc/INfcDta$Stub$Proxy;->enableClient(Ljava/lang/String;III)Z
+Landroid/nfc/INfcDta$Stub$Proxy;->enableDta()V
+Landroid/nfc/INfcDta$Stub$Proxy;->enableServer(Ljava/lang/String;IIII)Z
+Landroid/nfc/INfcDta$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcDta$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcDta$Stub$Proxy;->registerMessageService(Ljava/lang/String;)Z
+Landroid/nfc/INfcDta$Stub;-><init>()V
+Landroid/nfc/INfcDta$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcDta;
+Landroid/nfc/INfcDta$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableClient:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableDta:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_disableServer:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableClient:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableDta:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_enableServer:I
+Landroid/nfc/INfcDta$Stub;->TRANSACTION_registerMessageService:I
+Landroid/nfc/INfcDta;->disableClient()V
+Landroid/nfc/INfcDta;->disableDta()V
+Landroid/nfc/INfcDta;->disableServer()V
+Landroid/nfc/INfcDta;->enableClient(Ljava/lang/String;III)Z
+Landroid/nfc/INfcDta;->enableDta()V
+Landroid/nfc/INfcDta;->enableServer(Ljava/lang/String;IIII)Z
+Landroid/nfc/INfcDta;->registerMessageService(Ljava/lang/String;)Z
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->disableNfcFForegroundService()Z
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getMaxNumOfRegisterableSystemCodes()I
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcFServices(I)Ljava/util/List;
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
+Landroid/nfc/INfcFCardEmulation$Stub$Proxy;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcFCardEmulation$Stub;-><init>()V
+Landroid/nfc/INfcFCardEmulation$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/INfcFCardEmulation$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_disableNfcFForegroundService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_enableNfcFForegroundService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getMaxNumOfRegisterableSystemCodes:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcFServices:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getNfcid2ForService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_getSystemCodeForService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_registerSystemCodeForService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_removeSystemCodeForService:I
+Landroid/nfc/INfcFCardEmulation$Stub;->TRANSACTION_setNfcid2ForService:I
+Landroid/nfc/INfcFCardEmulation;->disableNfcFForegroundService()Z
+Landroid/nfc/INfcFCardEmulation;->enableNfcFForegroundService(Landroid/content/ComponentName;)Z
+Landroid/nfc/INfcFCardEmulation;->getMaxNumOfRegisterableSystemCodes()I
+Landroid/nfc/INfcFCardEmulation;->getNfcFServices(I)Ljava/util/List;
+Landroid/nfc/INfcFCardEmulation;->getNfcid2ForService(ILandroid/content/ComponentName;)Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation;->getSystemCodeForService(ILandroid/content/ComponentName;)Ljava/lang/String;
+Landroid/nfc/INfcFCardEmulation;->registerSystemCodeForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcFCardEmulation;->removeSystemCodeForService(ILandroid/content/ComponentName;)Z
+Landroid/nfc/INfcFCardEmulation;->setNfcid2ForService(ILandroid/content/ComponentName;Ljava/lang/String;)Z
+Landroid/nfc/INfcTag$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcTag$Stub$Proxy;->canMakeReadOnly(I)Z
+Landroid/nfc/INfcTag$Stub$Proxy;->connect(II)I
+Landroid/nfc/INfcTag$Stub$Proxy;->formatNdef(I[B)I
+Landroid/nfc/INfcTag$Stub$Proxy;->getExtendedLengthApdusSupported()Z
+Landroid/nfc/INfcTag$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcTag$Stub$Proxy;->getMaxTransceiveLength(I)I
+Landroid/nfc/INfcTag$Stub$Proxy;->getTechList(I)[I
+Landroid/nfc/INfcTag$Stub$Proxy;->getTimeout(I)I
+Landroid/nfc/INfcTag$Stub$Proxy;->isNdef(I)Z
+Landroid/nfc/INfcTag$Stub$Proxy;->isPresent(I)Z
+Landroid/nfc/INfcTag$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcTag$Stub$Proxy;->ndefIsWritable(I)Z
+Landroid/nfc/INfcTag$Stub$Proxy;->ndefMakeReadOnly(I)I
+Landroid/nfc/INfcTag$Stub$Proxy;->ndefRead(I)Landroid/nfc/NdefMessage;
+Landroid/nfc/INfcTag$Stub$Proxy;->ndefWrite(ILandroid/nfc/NdefMessage;)I
+Landroid/nfc/INfcTag$Stub$Proxy;->reconnect(I)I
+Landroid/nfc/INfcTag$Stub$Proxy;->rediscover(I)Landroid/nfc/Tag;
+Landroid/nfc/INfcTag$Stub$Proxy;->resetTimeouts()V
+Landroid/nfc/INfcTag$Stub$Proxy;->setTimeout(II)I
+Landroid/nfc/INfcTag$Stub$Proxy;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
+Landroid/nfc/INfcTag$Stub;-><init>()V
+Landroid/nfc/INfcTag$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcTag;
+Landroid/nfc/INfcTag$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_canMakeReadOnly:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_connect:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_formatNdef:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_getExtendedLengthApdusSupported:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_getMaxTransceiveLength:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTechList:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_getTimeout:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_isNdef:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_isPresent:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefIsWritable:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefMakeReadOnly:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefRead:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_ndefWrite:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_reconnect:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_rediscover:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_resetTimeouts:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_setTimeout:I
+Landroid/nfc/INfcTag$Stub;->TRANSACTION_transceive:I
+Landroid/nfc/INfcTag;->canMakeReadOnly(I)Z
+Landroid/nfc/INfcTag;->connect(II)I
+Landroid/nfc/INfcTag;->formatNdef(I[B)I
+Landroid/nfc/INfcTag;->getExtendedLengthApdusSupported()Z
+Landroid/nfc/INfcTag;->getMaxTransceiveLength(I)I
+Landroid/nfc/INfcTag;->getTechList(I)[I
+Landroid/nfc/INfcTag;->getTimeout(I)I
+Landroid/nfc/INfcTag;->isNdef(I)Z
+Landroid/nfc/INfcTag;->isPresent(I)Z
+Landroid/nfc/INfcTag;->ndefIsWritable(I)Z
+Landroid/nfc/INfcTag;->ndefMakeReadOnly(I)I
+Landroid/nfc/INfcTag;->ndefRead(I)Landroid/nfc/NdefMessage;
+Landroid/nfc/INfcTag;->ndefWrite(ILandroid/nfc/NdefMessage;)I
+Landroid/nfc/INfcTag;->reconnect(I)I
+Landroid/nfc/INfcTag;->rediscover(I)Landroid/nfc/Tag;
+Landroid/nfc/INfcTag;->resetTimeouts()V
+Landroid/nfc/INfcTag;->setTimeout(II)I
+Landroid/nfc/INfcTag;->transceive(I[BZ)Landroid/nfc/TransceiveResult;
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/INfcUnlockHandler$Stub$Proxy;->onUnlockAttempted(Landroid/nfc/Tag;)Z
+Landroid/nfc/INfcUnlockHandler$Stub;-><init>()V
+Landroid/nfc/INfcUnlockHandler$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/INfcUnlockHandler;
+Landroid/nfc/INfcUnlockHandler$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/INfcUnlockHandler$Stub;->TRANSACTION_onUnlockAttempted:I
+Landroid/nfc/INfcUnlockHandler;->onUnlockAttempted(Landroid/nfc/Tag;)Z
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->getInterfaceDescriptor()Ljava/lang/String;
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->mRemote:Landroid/os/IBinder;
+Landroid/nfc/ITagRemovedCallback$Stub$Proxy;->onTagRemoved()V
+Landroid/nfc/ITagRemovedCallback$Stub;-><init>()V
+Landroid/nfc/ITagRemovedCallback$Stub;->asInterface(Landroid/os/IBinder;)Landroid/nfc/ITagRemovedCallback;
+Landroid/nfc/ITagRemovedCallback$Stub;->DESCRIPTOR:Ljava/lang/String;
+Landroid/nfc/ITagRemovedCallback$Stub;->TRANSACTION_onTagRemoved:I
+Landroid/nfc/ITagRemovedCallback;->onTagRemoved()V
+Landroid/nfc/NdefMessage;->mRecords:[Landroid/nfc/NdefRecord;
+Landroid/nfc/NdefRecord;->bytesToString([B)Ljava/lang/StringBuilder;
+Landroid/nfc/NdefRecord;->EMPTY_BYTE_ARRAY:[B
+Landroid/nfc/NdefRecord;->ensureSanePayloadSize(J)V
+Landroid/nfc/NdefRecord;->FLAG_CF:B
+Landroid/nfc/NdefRecord;->FLAG_IL:B
+Landroid/nfc/NdefRecord;->FLAG_MB:B
+Landroid/nfc/NdefRecord;->FLAG_ME:B
+Landroid/nfc/NdefRecord;->FLAG_SR:B
+Landroid/nfc/NdefRecord;->getByteLength()I
+Landroid/nfc/NdefRecord;->MAX_PAYLOAD_SIZE:I
+Landroid/nfc/NdefRecord;->mPayload:[B
+Landroid/nfc/NdefRecord;->mTnf:S
+Landroid/nfc/NdefRecord;->mType:[B
+Landroid/nfc/NdefRecord;->parse(Ljava/nio/ByteBuffer;Z)[Landroid/nfc/NdefRecord;
+Landroid/nfc/NdefRecord;->parseWktUri()Landroid/net/Uri;
+Landroid/nfc/NdefRecord;->RTD_ANDROID_APP:[B
+Landroid/nfc/NdefRecord;->TNF_RESERVED:S
+Landroid/nfc/NdefRecord;->toUri(Z)Landroid/net/Uri;
+Landroid/nfc/NdefRecord;->URI_PREFIX_MAP:[Ljava/lang/String;
+Landroid/nfc/NdefRecord;->validateTnf(S[B[B[B)Ljava/lang/String;
+Landroid/nfc/NdefRecord;->writeToByteBuffer(Ljava/nio/ByteBuffer;ZZ)V
+Landroid/nfc/NfcActivityManager$NfcActivityState;->activity:Landroid/app/Activity;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->destroy()V
+Landroid/nfc/NfcActivityManager$NfcActivityState;->flags:I
+Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessage:Landroid/nfc/NdefMessage;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->ndefMessageCallback:Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->onNdefPushCompleteCallback:Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->readerCallback:Landroid/nfc/NfcAdapter$ReaderCallback;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeExtras:Landroid/os/Bundle;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->readerModeFlags:I
+Landroid/nfc/NfcActivityManager$NfcActivityState;->resumed:Z
+Landroid/nfc/NfcActivityManager$NfcActivityState;->token:Landroid/os/Binder;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->uriCallback:Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;
+Landroid/nfc/NfcActivityManager$NfcActivityState;->uris:[Landroid/net/Uri;
+Landroid/nfc/NfcActivityManager$NfcApplicationState;->app:Landroid/app/Application;
+Landroid/nfc/NfcActivityManager$NfcApplicationState;->refCount:I
+Landroid/nfc/NfcActivityManager$NfcApplicationState;->register()V
+Landroid/nfc/NfcActivityManager$NfcApplicationState;->unregister()V
+Landroid/nfc/NfcActivityManager;-><init>(Landroid/nfc/NfcAdapter;)V
+Landroid/nfc/NfcActivityManager;->createBeamShareData(B)Landroid/nfc/BeamShareData;
+Landroid/nfc/NfcActivityManager;->DBG:Ljava/lang/Boolean;
+Landroid/nfc/NfcActivityManager;->destroyActivityState(Landroid/app/Activity;)V
+Landroid/nfc/NfcActivityManager;->disableReaderMode(Landroid/app/Activity;)V
+Landroid/nfc/NfcActivityManager;->enableReaderMode(Landroid/app/Activity;Landroid/nfc/NfcAdapter$ReaderCallback;ILandroid/os/Bundle;)V
+Landroid/nfc/NfcActivityManager;->findActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
+Landroid/nfc/NfcActivityManager;->findAppState(Landroid/app/Application;)Landroid/nfc/NfcActivityManager$NfcApplicationState;
+Landroid/nfc/NfcActivityManager;->findResumedActivityState()Landroid/nfc/NfcActivityManager$NfcActivityState;
+Landroid/nfc/NfcActivityManager;->getActivityState(Landroid/app/Activity;)Landroid/nfc/NfcActivityManager$NfcActivityState;
+Landroid/nfc/NfcActivityManager;->mActivities:Ljava/util/List;
+Landroid/nfc/NfcActivityManager;->mApps:Ljava/util/List;
+Landroid/nfc/NfcActivityManager;->onNdefPushComplete(B)V
+Landroid/nfc/NfcActivityManager;->onTagDiscovered(Landroid/nfc/Tag;)V
+Landroid/nfc/NfcActivityManager;->registerApplication(Landroid/app/Application;)V
+Landroid/nfc/NfcActivityManager;->requestNfcServiceCallback()V
+Landroid/nfc/NfcActivityManager;->setNdefPushContentUri(Landroid/app/Activity;[Landroid/net/Uri;)V
+Landroid/nfc/NfcActivityManager;->setNdefPushContentUriCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateBeamUrisCallback;)V
+Landroid/nfc/NfcActivityManager;->setNdefPushMessage(Landroid/app/Activity;Landroid/nfc/NdefMessage;I)V
+Landroid/nfc/NfcActivityManager;->setNdefPushMessageCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$CreateNdefMessageCallback;I)V
+Landroid/nfc/NfcActivityManager;->setOnNdefPushCompleteCallback(Landroid/app/Activity;Landroid/nfc/NfcAdapter$OnNdefPushCompleteCallback;)V
+Landroid/nfc/NfcActivityManager;->setReaderMode(Landroid/os/Binder;ILandroid/os/Bundle;)V
+Landroid/nfc/NfcActivityManager;->TAG:Ljava/lang/String;
+Landroid/nfc/NfcActivityManager;->unregisterApplication(Landroid/app/Application;)V
+Landroid/nfc/NfcActivityManager;->verifyNfcPermission()V
+Landroid/nfc/NfcAdapter;-><init>(Landroid/content/Context;)V
+Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_DONE:Ljava/lang/String;
+Landroid/nfc/NfcAdapter;->ACTION_HANDOVER_TRANSFER_STARTED:Ljava/lang/String;
+Landroid/nfc/NfcAdapter;->ACTION_TAG_LEFT_FIELD:Ljava/lang/String;
+Landroid/nfc/NfcAdapter;->disableForegroundDispatchInternal(Landroid/app/Activity;Z)V
+Landroid/nfc/NfcAdapter;->dispatch(Landroid/nfc/Tag;)V
+Landroid/nfc/NfcAdapter;->enforceResumed(Landroid/app/Activity;)V
+Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_STATUS:Ljava/lang/String;
+Landroid/nfc/NfcAdapter;->EXTRA_HANDOVER_TRANSFER_URI:Ljava/lang/String;
+Landroid/nfc/NfcAdapter;->getCardEmulationService()Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/NfcAdapter;->getNfcDtaInterface()Landroid/nfc/INfcDta;
+Landroid/nfc/NfcAdapter;->getNfcFCardEmulationService()Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/NfcAdapter;->getSdkVersion()I
+Landroid/nfc/NfcAdapter;->getServiceInterface()Landroid/nfc/INfcAdapter;
+Landroid/nfc/NfcAdapter;->getTagService()Landroid/nfc/INfcTag;
+Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_FAILURE:I
+Landroid/nfc/NfcAdapter;->HANDOVER_TRANSFER_STATUS_SUCCESS:I
+Landroid/nfc/NfcAdapter;->hasNfcFeature()Z
+Landroid/nfc/NfcAdapter;->hasNfcHceFeature()Z
+Landroid/nfc/NfcAdapter;->invokeBeam(Landroid/nfc/BeamShareData;)Z
+Landroid/nfc/NfcAdapter;->mContext:Landroid/content/Context;
+Landroid/nfc/NfcAdapter;->mForegroundDispatchListener:Landroid/app/OnActivityPausedListener;
+Landroid/nfc/NfcAdapter;->mLock:Ljava/lang/Object;
+Landroid/nfc/NfcAdapter;->mNfcActivityManager:Landroid/nfc/NfcActivityManager;
+Landroid/nfc/NfcAdapter;->mNfcUnlockHandlers:Ljava/util/HashMap;
+Landroid/nfc/NfcAdapter;->mTagRemovedListener:Landroid/nfc/ITagRemovedCallback;
+Landroid/nfc/NfcAdapter;->pausePolling(I)V
+Landroid/nfc/NfcAdapter;->resumePolling()V
+Landroid/nfc/NfcAdapter;->sCardEmulationService:Landroid/nfc/INfcCardEmulation;
+Landroid/nfc/NfcAdapter;->setP2pModes(II)V
+Landroid/nfc/NfcAdapter;->sHasNfcFeature:Z
+Landroid/nfc/NfcAdapter;->sIsInitialized:Z
+Landroid/nfc/NfcAdapter;->sNfcAdapters:Ljava/util/HashMap;
+Landroid/nfc/NfcAdapter;->sNfcFCardEmulationService:Landroid/nfc/INfcFCardEmulation;
+Landroid/nfc/NfcAdapter;->sNullContextNfcAdapter:Landroid/nfc/NfcAdapter;
+Landroid/nfc/NfcAdapter;->sTagService:Landroid/nfc/INfcTag;
+Landroid/nfc/NfcAdapter;->TAG:Ljava/lang/String;
+Landroid/nfc/NfcEvent;-><init>(Landroid/nfc/NfcAdapter;B)V
+Landroid/nfc/NfcManager;->mAdapter:Landroid/nfc/NfcAdapter;
+Landroid/nfc/Tag;-><init>([B[I[Landroid/os/Bundle;ILandroid/nfc/INfcTag;)V
+Landroid/nfc/Tag;->createMockTag([B[I[Landroid/os/Bundle;)Landroid/nfc/Tag;
+Landroid/nfc/Tag;->generateTechStringList([I)[Ljava/lang/String;
+Landroid/nfc/Tag;->getConnectedTechnology()I
+Landroid/nfc/Tag;->getTechCodeList()[I
+Landroid/nfc/Tag;->getTechCodesFromStrings([Ljava/lang/String;)[I
+Landroid/nfc/Tag;->getTechExtras(I)Landroid/os/Bundle;
+Landroid/nfc/Tag;->getTechStringToCodeMap()Ljava/util/HashMap;
+Landroid/nfc/Tag;->hasTech(I)Z
+Landroid/nfc/Tag;->mConnectedTechnology:I
+Landroid/nfc/Tag;->mServiceHandle:I
+Landroid/nfc/Tag;->mTagService:Landroid/nfc/INfcTag;
+Landroid/nfc/Tag;->mTechExtras:[Landroid/os/Bundle;
+Landroid/nfc/Tag;->mTechList:[I
+Landroid/nfc/Tag;->mTechStringList:[Ljava/lang/String;
+Landroid/nfc/Tag;->readBytesWithNull(Landroid/os/Parcel;)[B
+Landroid/nfc/Tag;->rediscover()Landroid/nfc/Tag;
+Landroid/nfc/Tag;->setConnectedTechnology(I)V
+Landroid/nfc/Tag;->setTechnologyDisconnected()V
+Landroid/nfc/Tag;->writeBytesWithNull(Landroid/os/Parcel;[B)V
+Landroid/nfc/tech/BasicTagTechnology;-><init>(Landroid/nfc/Tag;I)V
+Landroid/nfc/tech/BasicTagTechnology;->checkConnected()V
+Landroid/nfc/tech/BasicTagTechnology;->getMaxTransceiveLengthInternal()I
+Landroid/nfc/tech/BasicTagTechnology;->mIsConnected:Z
+Landroid/nfc/tech/BasicTagTechnology;->mSelectedTechnology:I
+Landroid/nfc/tech/BasicTagTechnology;->mTag:Landroid/nfc/Tag;
+Landroid/nfc/tech/BasicTagTechnology;->reconnect()V
+Landroid/nfc/tech/BasicTagTechnology;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/BasicTagTechnology;->transceive([BZ)[B
+Landroid/nfc/tech/IsoDep;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/IsoDep;->EXTRA_HIST_BYTES:Ljava/lang/String;
+Landroid/nfc/tech/IsoDep;->EXTRA_HI_LAYER_RESP:Ljava/lang/String;
+Landroid/nfc/tech/IsoDep;->mHiLayerResponse:[B
+Landroid/nfc/tech/IsoDep;->mHistBytes:[B
+Landroid/nfc/tech/IsoDep;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/MifareClassic;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/MifareClassic;->authenticate(I[BZ)Z
+Landroid/nfc/tech/MifareClassic;->isEmulated()Z
+Landroid/nfc/tech/MifareClassic;->MAX_BLOCK_COUNT:I
+Landroid/nfc/tech/MifareClassic;->MAX_SECTOR_COUNT:I
+Landroid/nfc/tech/MifareClassic;->mIsEmulated:Z
+Landroid/nfc/tech/MifareClassic;->mSize:I
+Landroid/nfc/tech/MifareClassic;->mType:I
+Landroid/nfc/tech/MifareClassic;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/MifareClassic;->validateBlock(I)V
+Landroid/nfc/tech/MifareClassic;->validateSector(I)V
+Landroid/nfc/tech/MifareClassic;->validateValueOperand(I)V
+Landroid/nfc/tech/MifareUltralight;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/MifareUltralight;->EXTRA_IS_UL_C:Ljava/lang/String;
+Landroid/nfc/tech/MifareUltralight;->MAX_PAGE_COUNT:I
+Landroid/nfc/tech/MifareUltralight;->mType:I
+Landroid/nfc/tech/MifareUltralight;->NXP_MANUFACTURER_ID:I
+Landroid/nfc/tech/MifareUltralight;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/MifareUltralight;->validatePageIndex(I)V
+Landroid/nfc/tech/Ndef;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/Ndef;->EXTRA_NDEF_CARDSTATE:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MAXLENGTH:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->EXTRA_NDEF_MSG:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->EXTRA_NDEF_TYPE:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->ICODE_SLI:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->mCardState:I
+Landroid/nfc/tech/Ndef;->mMaxNdefSize:I
+Landroid/nfc/tech/Ndef;->mNdefMsg:Landroid/nfc/NdefMessage;
+Landroid/nfc/tech/Ndef;->mNdefType:I
+Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_ONLY:I
+Landroid/nfc/tech/Ndef;->NDEF_MODE_READ_WRITE:I
+Landroid/nfc/tech/Ndef;->NDEF_MODE_UNKNOWN:I
+Landroid/nfc/tech/Ndef;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/Ndef;->TYPE_1:I
+Landroid/nfc/tech/Ndef;->TYPE_2:I
+Landroid/nfc/tech/Ndef;->TYPE_3:I
+Landroid/nfc/tech/Ndef;->TYPE_4:I
+Landroid/nfc/tech/Ndef;->TYPE_ICODE_SLI:I
+Landroid/nfc/tech/Ndef;->TYPE_MIFARE_CLASSIC:I
+Landroid/nfc/tech/Ndef;->TYPE_OTHER:I
+Landroid/nfc/tech/Ndef;->UNKNOWN:Ljava/lang/String;
+Landroid/nfc/tech/NdefFormatable;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NdefFormatable;->format(Landroid/nfc/NdefMessage;Z)V
+Landroid/nfc/tech/NdefFormatable;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/NfcA;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NfcA;->EXTRA_ATQA:Ljava/lang/String;
+Landroid/nfc/tech/NfcA;->EXTRA_SAK:Ljava/lang/String;
+Landroid/nfc/tech/NfcA;->mAtqa:[B
+Landroid/nfc/tech/NfcA;->mSak:S
+Landroid/nfc/tech/NfcA;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/NfcB;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NfcB;->EXTRA_APPDATA:Ljava/lang/String;
+Landroid/nfc/tech/NfcB;->EXTRA_PROTINFO:Ljava/lang/String;
+Landroid/nfc/tech/NfcB;->mAppData:[B
+Landroid/nfc/tech/NfcB;->mProtInfo:[B
+Landroid/nfc/tech/NfcBarcode;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NfcBarcode;->EXTRA_BARCODE_TYPE:Ljava/lang/String;
+Landroid/nfc/tech/NfcBarcode;->mType:I
+Landroid/nfc/tech/NfcF;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NfcF;->EXTRA_PMM:Ljava/lang/String;
+Landroid/nfc/tech/NfcF;->EXTRA_SC:Ljava/lang/String;
+Landroid/nfc/tech/NfcF;->mManufacturer:[B
+Landroid/nfc/tech/NfcF;->mSystemCode:[B
+Landroid/nfc/tech/NfcF;->TAG:Ljava/lang/String;
+Landroid/nfc/tech/NfcV;-><init>(Landroid/nfc/Tag;)V
+Landroid/nfc/tech/NfcV;->EXTRA_DSFID:Ljava/lang/String;
+Landroid/nfc/tech/NfcV;->EXTRA_RESP_FLAGS:Ljava/lang/String;
+Landroid/nfc/tech/NfcV;->mDsfId:B
+Landroid/nfc/tech/NfcV;->mRespFlags:B
+Landroid/nfc/tech/TagTechnology;->ISO_DEP:I
+Landroid/nfc/tech/TagTechnology;->MIFARE_CLASSIC:I
+Landroid/nfc/tech/TagTechnology;->MIFARE_ULTRALIGHT:I
+Landroid/nfc/tech/TagTechnology;->NDEF:I
+Landroid/nfc/tech/TagTechnology;->NDEF_FORMATABLE:I
+Landroid/nfc/tech/TagTechnology;->NFC_A:I
+Landroid/nfc/tech/TagTechnology;->NFC_B:I
+Landroid/nfc/tech/TagTechnology;->NFC_BARCODE:I
+Landroid/nfc/tech/TagTechnology;->NFC_F:I
+Landroid/nfc/tech/TagTechnology;->NFC_V:I
+Landroid/nfc/tech/TagTechnology;->reconnect()V
+Landroid/nfc/TechListParcel;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/nfc/TechListParcel;->getTechLists()[[Ljava/lang/String;
+Landroid/nfc/TechListParcel;->mTechLists:[[Ljava/lang/String;
+Landroid/nfc/TransceiveResult;-><init>(I[B)V
+Landroid/nfc/TransceiveResult;->CREATOR:Landroid/os/Parcelable$Creator;
+Landroid/nfc/TransceiveResult;->getResponseOrThrow()[B
+Landroid/nfc/TransceiveResult;->mResponseData:[B
+Landroid/nfc/TransceiveResult;->mResult:I
+Landroid/nfc/TransceiveResult;->RESULT_EXCEEDED_LENGTH:I
+Landroid/nfc/TransceiveResult;->RESULT_FAILURE:I
+Landroid/nfc/TransceiveResult;->RESULT_SUCCESS:I
+Landroid/nfc/TransceiveResult;->RESULT_TAGLOST:I
diff --git a/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt b/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
new file mode 100644
index 0000000..f53aa1c
--- /dev/null
+++ b/apex/hiddenapi/hiddenapi-max-target-r-loprio.txt
@@ -0,0 +1 @@
+Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I
diff --git a/com.android.nfc.xml b/com.android.nfc.xml
index 320d433..2046ab5 100644
--- a/com.android.nfc.xml
+++ b/com.android.nfc.xml
@@ -31,5 +31,9 @@
<permission name="android.permission.DISPATCH_NFC_MESSAGE"/>
<permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
+ <permission name="android.permission.SHOW_CUSTOMIZED_RESOLVER"/>
+ <permission name="android.permission.DUMP"/>
+ <permission name="android.permission.QUERY_CLONED_APPS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
</privapp-permissions>
</permissions>
diff --git a/flags/Android.bp b/flags/Android.bp
index 0ceb60b..8a201f1 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_fwk_nfc",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/jarjar-rules.txt b/jarjar-rules.txt
index 655c09e..677424a 100644
--- a/jarjar-rules.txt
+++ b/jarjar-rules.txt
@@ -11,3 +11,33 @@
# com.google.android.material_material
rule com.google.android.material.** com.android.nfc.x.@0
rule com.android.internal.util.FastXmlSerializer* com.android.nfc.x.@0
+
+# Used for proto debug dumping
+rule android.app.PendingIntentProto* com.android.nfc.x.@0
+rule android.content.ComponentNameProto* com.android.nfc.x.@0
+rule android.content.IntentProto* com.android.nfc.x.@0
+rule android.content.IntentFilterProto* com.android.nfc.x.@0
+rule android.content.AuthorityEntryProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.AidGroupProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.ApduServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.cardemulation.NfcFServiceInfoProto* com.android.nfc.x.@0
+rule android.nfc.NdefMessageProto* com.android.nfc.x.@0
+rule android.nfc.NdefRecordProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.CardEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredNfcFServicesCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.PreferredServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.EnabledNfcFServicesProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredAidCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.AidRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.RegisteredT3tIdentifiersCacheProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.SystemCodeRoutingManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.cardemulation.HostNfcFEmulationManagerProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcServiceDumpProto* com.android.nfc.x.@0
+rule com.android.nfc.DiscoveryParamsProto* com.android.nfc.x.@0
+rule com.android.nfc.NfcDispatcherProto* com.android.nfc.x.@0
+rule android.os.PersistableBundleProto* com.android.nfc.x.@0
+
+# Core utils available for modules
+rule com.android.modules.utils.** com.android.nfc.x.@0
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..0f1aefa
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,1742 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" UserHandle.getUserHandleForUid(info.serviceInfo.getUid()).getIdentifier(),"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="181"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" info.serviceInfo.getComponent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="182"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast from `ApduServiceInfo` to `Parcelable` requires API level 35 (current min is 34)"
+ errorLine1=" dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="185"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=" CharSequence label = service.getDescription();"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="217"
+ column="46"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#loadLabel`"
+ errorLine1=" if (label == null) label = service.loadLabel(pm);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="218"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#loadIcon`"
+ errorLine1=" Drawable icon = pm.getUserBadgedIcon(service.loadIcon(pm),"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="220"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" UserHandle.getUserHandleForUid(service.getUid()));"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="221"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#loadBanner`"
+ errorLine1=" banner = service.loadBanner(pm);"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/AppChooserActivity.java"
+ line="225"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#hasCategory`"
+ errorLine1=" if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/CardEmulationManager.java"
+ line="372"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" && wasServicePreInstalled(pm, service.getComponent())) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/CardEmulationManager.java"
+ line="373"
+ column="67"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" lastFoundPaymentService = service.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/CardEmulationManager.java"
+ line="375"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getSystemCode`"
+ errorLine1=' if (serviceInfo.getSystemCode().equalsIgnoreCase("NULL") ||'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/EnabledNfcFServices.java"
+ line="130"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=' serviceInfo.getNfcid2().equalsIgnoreCase("NULL") ||'
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/EnabledNfcFServices.java"
+ line="131"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getT3tPmm`"
+ errorLine1=' serviceInfo.getT3tPmm().equalsIgnoreCase("NULL")) {'
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/EnabledNfcFServices.java"
+ line="132"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" mStatsdUtils.setCardEmulationEventUid(defaultServiceInfo.getUid());"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="247"
+ column="82"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresUnlock`"
+ errorLine1=" if ((defaultServiceInfo.requiresUnlock()"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="250"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" if (defaultServiceInfo.requiresScreenOn() && !mPowerManager.isScreenOn()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="262"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (!defaultServiceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="272"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" resolvedService = defaultServiceInfo.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="281"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" if (mActiveServiceName.equals(serviceInfo.getComponent())) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="285"
+ column="67"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" int uid = resolvedServiceInfo.getUid();"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="310"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" UserHandle.getUserHandleForUid(resolvedServiceInfo.getUid());"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="354"
+ column="84"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Cast from `ApduServiceInfo` to `Parcelable` requires API level 35 (current min is 34)"
+ errorLine1=" dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="557"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" UserHandle.getUserHandleForUid(service.getUid()));"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostEmulationManager.java"
+ line="560"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=" resolvedServiceName = resolvedService.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java"
+ line="124"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" int uid = resolvedService != null ? resolvedService.getUid() : -1;"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java"
+ line="146"
+ column="73"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.service.chooser.CustomChoosers#createNfcResolverIntent`"
+ errorLine1=" CustomChoosers.createNfcResolverIntent(intent, null, filtered);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java"
+ line="312"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.service.chooser.CustomChoosers#createNfcResolverIntent`"
+ errorLine1=" intent = CustomChoosers.createNfcResolverIntent("
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/NfcDispatcher.java"
+ line="921"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.se.omapi.SeFrameworkInitializer#getSeServiceManager`"
+ errorLine1=" SeServiceManager manager = SeFrameworkInitializer.getSeServiceManager();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/NfcService.java"
+ line="938"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.se.omapi.SeServiceManager#getSeManagerServiceRegisterer`"
+ errorLine1=" manager.getSeManagerServiceRegisterer().get());"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/NfcService.java"
+ line="944"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.se.omapi.SeServiceManager.ServiceRegisterer#get`"
+ errorLine1=" manager.getSeManagerServiceRegisterer().get());"
+ errorLine2=" ~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/NfcService.java"
+ line="944"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#hasCategory`"
+ errorLine1=" if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/PreferredServices.java"
+ line="301"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" final List<String> otherAids = serviceInfo.getAids();"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/PreferredServices.java"
+ line="311"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' "service=" + service.getComponent() +'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="92"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" return service.equals(resolveInfo.defaultService.getComponent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="249"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" return service.equals(resolveInfo.services.get(0).getComponent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="251"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" int userId = UserHandle.getUserHandleForUid(serviceAidInfo.service.getUid())"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="292"
+ column="80"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" ComponentName componentName = serviceAidInfo.service.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="294"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" serviceAidInfo.service.getComponent()"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="307"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" serviceAidInfo.service.getComponent() +"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="325"
+ column="52"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isCategoryOtherServiceEnabled`"
+ errorLine1=" if (serviceAidInfo.service.isCategoryOtherServiceEnabled()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="328"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" if (DBG) Log.d(TAG, serviceAidInfo.service.getComponent() +"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="329"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' defaultWalletServices.get(0).getComponent() + " default.");'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="350"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' resolveInfo.services.get(0).getComponent() + " default.");'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="362"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" int userId = UserHandle.getUserHandleForUid(serviceAidInfo.service.getUid())"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="384"
+ column="80"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" ComponentName componentName = serviceAidInfo.service.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="386"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" serviceAidInfo.service.getComponent()"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="392"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' aidDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="419"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' aidDefaultInfo.paymentDefault.service.getComponent() + " is payment" +'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="458"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="526"
+ column="80"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getPrefixAids`"
+ errorLine1=" List<String> prefixAids = service.getPrefixAids();"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="527"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getSubsetAids`"
+ errorLine1=" List<String> subSetAids = service.getSubsetAids();"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="528"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" for (String aid : service.getAids()) {"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="530"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getCategoryForAid`"
+ errorLine1=" serviceAidInfo.category = service.getCategoryForAid(aid);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="580"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getPrefixAids`"
+ errorLine1=" for (String prefixAid : service.getPrefixAids()) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="632"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" int userId = UserHandle.getUserHandleForUid(service.getUid())"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="637"
+ column="76"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getCategoryForAid`"
+ errorLine1=" .equals(service.getCategoryForAid(prefixAid)) ||"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="640"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" (service.getComponent().equals(mPreferredForegroundService) &&"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="641"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getSubsetAids`"
+ errorLine1=" for (String aid : resolveInfo.defaultService.getSubsetAids()) {"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="756"
+ column="66"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" getUserHandleForUid(resolveInfo.defaultService.getUid())."
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="759"
+ column="84"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getCategoryForAid`"
+ errorLine1=" equals(resolveInfo.defaultService.getCategoryForAid(aid))) ||"
+ errorLine2=" ~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="762"
+ column="69"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" (resolveInfo.defaultService.getComponent()."
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="763"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" aidType.isOnHost = resolveInfo.defaultService.isOnHost();"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="982"
+ column="63"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" resolveInfo.defaultService.getOffHostSecureElement();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="985"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresUnlock`"
+ errorLine1=" boolean requiresUnlock = resolveInfo.defaultService.requiresUnlock();"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="988"
+ column="69"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" boolean requiresScreenOn = resolveInfo.defaultService.requiresScreenOn();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="989"
+ column="71"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" aidType.isOnHost = resolveInfo.services.get(0).isOnHost();"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1002"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" resolveInfo.services.get(0).getOffHostSecureElement();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1005"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresUnlock`"
+ errorLine1=" boolean requiresUnlock = resolveInfo.services.get(0).requiresUnlock();"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1009"
+ column="70"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" boolean requiresScreenOn = resolveInfo.services.get(0).requiresScreenOn();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1010"
+ column="72"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" onHost |= service.isOnHost();"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1026"
+ column="39"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" offHostSE = service.getOffHostSecureElement();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1029"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresUnlock`"
+ errorLine1=" requiresUnlock = service.requiresUnlock();"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1030"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" requiresScreenOn = service.requiresScreenOn();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1031"
+ column="56"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" service.getOffHostSecureElement())) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1033"
+ column="41"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresUnlock`"
+ errorLine1=" } else if (requiresUnlock != service.requiresUnlock()"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1041"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" || requiresScreenOn != service.requiresScreenOn()) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1042"
+ column="64"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#requiresScreenOn`"
+ errorLine1=" requiresScreenOnServiceExist |= service.requiresScreenOn();"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1053"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" defaultServiceInfo.getComponent() : null;"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1148"
+ column="36"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=' " (Description: " + serviceInfo.getDescription() + ")\n");'
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1156"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" defaultServiceInfo.getComponent() : null;"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1191"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#dumpDebug`"
+ errorLine1=" serviceInfo.dumpDebug(proto);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredAidCache.java"
+ line="1199"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=" if (service.getComponent().equals(componentName)) return true;"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="209"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.NfcFServiceInfo`"
+ errorLine1=" NfcFServiceInfo service = new NfcFServiceInfo(pm, resolvedService);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="270"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=" userServices.services.put(service.getComponent(), service);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="334"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=' if (DBG) Log.d(TAG, "Added service: " + service.getComponent());'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="335"
+ column="65"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=" userServices.services.remove(service.getComponent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="338"
+ column="54"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=' if (DBG) Log.d(TAG, "Removed service: " + service.getComponent());'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="339"
+ column="67"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service == null || (service.getUid() != dynamicSystemCode.uid)) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="350"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#setDynamicSystemCode`"
+ errorLine1=" service.setDynamicSystemCode(dynamicSystemCode.systemCode);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="354"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service == null || (service.getUid() != dynamicNfcid2.uid)) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="366"
+ column="49"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#setDynamicNfcid2`"
+ errorLine1=" service.setDynamicNfcid2(dynamicNfcid2.nfcid2);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="370"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=' if (service.getNfcid2().equalsIgnoreCase("RANDOM")) {'
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="388"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#setDynamicNfcid2`"
+ errorLine1=" service.setDynamicNfcid2(randomNfcid2);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="390"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" new DynamicNfcid2(service.getUid(), randomNfcid2);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="392"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="564"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#setDynamicSystemCode`"
+ errorLine1=" service.setDynamicSystemCode(systemCode);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="585"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="608"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getSystemCode`"
+ errorLine1=" return service.getSystemCode();"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="612"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="641"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#setDynamicNfcid2`"
+ errorLine1=" service.setDynamicNfcid2(nfcid2);"
+ errorLine2=" ~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="660"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getUid`"
+ errorLine1=" if (service.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="683"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=" return service.getNfcid2();"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="687"
+ column="28"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#dump`"
+ errorLine1=" service.dump(pFd, pw, args);"
+ errorLine2=" ~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="765"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#dumpDebug`"
+ errorLine1=" service.dumpDebug(proto);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java"
+ line="791"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" if (service.getComponent().equals(serviceName)) return true;"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="261"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#hasCategory`"
+ errorLine1=" if (service.hasCategory(category)) services.add(service);"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="291"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.ApduServiceInfo`"
+ errorLine1=" ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="344"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="381"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=' " AIDs: " + service.getAids());'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="382"
+ column="45"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" userServices.services.put(service.getComponent(), service);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="383"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo == null || (serviceInfo.getUid() != dynamicSettings.uid)) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="394"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setDynamicAidGroup`"
+ errorLine1=" serviceInfo.setDynamicAidGroup(group);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="399"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setOffHostSecureElement`"
+ errorLine1=" serviceInfo.setOffHostSecureElement(dynamicSettings.offHostSE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="402"
+ column="37"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' Log.d(TAG, "update valid otherService: " + service.getComponent()'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="455"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=' + " AIDs: " + service.getAids());'
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="456"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#hasCategory`"
+ errorLine1=" if (!service.hasCategory(CardEmulation.CATEGORY_OTHER)) {"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="457"
+ column="30"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" ComponentName component = service.getComponent();"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="462"
+ column="51"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" status = new OtherServiceStatus(service.getUid(), isChecked);"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="467"
+ column="61"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setCategoryOtherServiceEnabled`"
+ errorLine1=" service.setCategoryOtherServiceEnabled(status.checked);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="472"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#createFromXml`"
+ errorLine1=" AidGroup group = AidGroup.createFromXml(parser);"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="545"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getCategory`"
+ errorLine1=" dynSettings.aidGroups.put(group.getCategory(), group);"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="561"
+ column="69"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#writeAsXml`"
+ errorLine1=" group.writeAsXml(out);"
+ errorLine2=" ~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="690"
+ column="31"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="778"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (offHostSE == null || serviceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="786"
+ column="50"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setOffHostSecureElement`"
+ errorLine1=" serviceInfo.setOffHostSecureElement(offHostSE);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="803"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="821"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" if (serviceInfo.isOnHost() || serviceInfo.getOffHostSecureElement() == null) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="829"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (serviceInfo.isOnHost() || serviceInfo.getOffHostSecureElement() == null) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="829"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#resetOffHostSecureElement`"
+ errorLine1=" serviceInfo.resetOffHostSecureElement();"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="844"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="862"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="893"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+ errorLine1=" List<String> aids = aidGroup.getAids();"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="903"
+ column="42"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setDynamicAidGroup`"
+ errorLine1=" serviceInfo.setDynamicAidGroup(aidGroup);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="910"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getCategory`"
+ errorLine1=" dynSettings.aidGroups.put(aidGroup.getCategory(), aidGroup);"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="917"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getCategory`"
+ errorLine1=" dynSettings.aidGroups.remove(aidGroup.getCategory());"
+ errorLine2=" ~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="925"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="974"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDynamicAidGroupForCategory`"
+ errorLine1=" return serviceInfo.getDynamicAidGroupForCategory(category);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="978"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" if (serviceInfo.getUid() != uid) {"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="993"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#removeDynamicAidGroupForCategory`"
+ errorLine1=" if (!serviceInfo.removeDynamicAidGroupForCategory(category)) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="998"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=" OtherServiceStatus status = userServices.others.get(service.getComponent());"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1036"
+ column="69"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getComponent`"
+ errorLine1=' Log.d(TAG, service.getComponent() + " status is could not be null");'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1039"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isCategoryOtherServiceEnabled`"
+ errorLine1=" if (service.isCategoryOtherServiceEnabled() == checked) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1043"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#setCategoryOtherServiceEnabled`"
+ errorLine1=" service.setCategoryOtherServiceEnabled(checked);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1048"
+ column="17"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#dump`"
+ errorLine1=" service.dump(pFd, pw, args);"
+ errorLine2=" ~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1066"
+ column="33"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#dumpDebug`"
+ errorLine1=" service.dumpDebug(proto);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredServicesCache.java"
+ line="1091"
+ column="21"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=" if (mEnabledForegroundService.equals(service.getComponent())) {"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="126"
+ column="62"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getSystemCode`"
+ errorLine1=' if (!service.getSystemCode().equalsIgnoreCase("NULL") &&'
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="127"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=' !service.getNfcid2().equalsIgnoreCase("NULL")) {'
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="128"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=" mForegroundT3tIdentifiersCache.put(service.getNfcid2(), service);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="129"
+ column="68"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getComponent`"
+ errorLine1=' "/" + entry.getValue().getComponent().toString());'
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="142"
+ column="48"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getNfcid2`"
+ errorLine1=" entry.getValue().getSystemCode(), entry.getValue().getNfcid2(), entry.getValue().getT3tPmm()));"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="169"
+ column="72"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getSystemCode`"
+ errorLine1=" entry.getValue().getSystemCode(), entry.getValue().getNfcid2(), entry.getValue().getT3tPmm()));"
+ errorLine2=" ~~~~~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="169"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#getT3tPmm`"
+ errorLine1=" entry.getValue().getSystemCode(), entry.getValue().getNfcid2(), entry.getValue().getT3tPmm()));"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="169"
+ column="102"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#dump`"
+ errorLine1=" entry.getValue().dump(pFd, pw, args);"
+ errorLine2=" ~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="245"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.NfcFServiceInfo#dumpDebug`"
+ errorLine1=" serviceInfo.dumpDebug(proto);"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java"
+ line="269"
+ column="25"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#loadIcon`"
+ errorLine1=" Drawable icon = pm.getUserBadgedIcon(serviceInfo.loadIcon(pm),"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/TapAgainDialog.java"
+ line="85"
+ column="58"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getUid`"
+ errorLine1=" UserHandle.getUserHandleForUid(serviceInfo.getUid()));"
+ errorLine2=" ~~~~~~">
+ <location
+ file="packages/apps/Nfc/src/com/android/nfc/cardemulation/TapAgainDialog.java"
+ line="86"
+ column="60"/>
+ </issue>
+
+</issues>
\ No newline at end of file
diff --git a/nci/jni/Android.bp b/nci/jni/Android.bp
index 88d38a2..ab3e5e9 100644
--- a/nci/jni/Android.bp
+++ b/nci/jni/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_fwk_nfc",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -59,7 +60,7 @@
debuggable: {
cflags: [
"-DDCHECK_ALWAYS_ON",
- "-DDTA_ENABLED"
+ "-DDTA_ENABLED",
],
},
},
@@ -70,8 +71,8 @@
},
stl: "libc++_static",
apex_available: [
- "//apex_available:platform",
- "com.android.nfcservices"
+ "//apex_available:platform",
+ "com.android.nfcservices",
],
required: [
// Provide a default libnfc-nci.conf in /system/etc for devices that
@@ -97,7 +98,7 @@
],
header_libs: [
- "jni_headers"
+ "jni_headers",
],
include_dirs: [
diff --git a/nci/jni/JavaClassConstants.h b/nci/jni/JavaClassConstants.h
index b619e74..dfe7687 100644
--- a/nci/jni/JavaClassConstants.h
+++ b/nci/jni/JavaClassConstants.h
@@ -29,7 +29,8 @@
extern jmethodID gCachedNfcManagerNotifyEeUpdated;
-extern const char* gNativeP2pDeviceClassName;
+extern jmethodID gCachedNfcManagerNotifyWlcStopped;
+
extern const char* gNativeNfcTagClassName;
extern const char* gNativeNfcManagerClassName;
} // namespace android
diff --git a/nci/jni/NativeNfcManager.cpp b/nci/jni/NativeNfcManager.cpp
index c5478d8..932faa6 100644
--- a/nci/jni/NativeNfcManager.cpp
+++ b/nci/jni/NativeNfcManager.cpp
@@ -26,6 +26,7 @@
#include "HciEventManager.h"
#include "JavaClassConstants.h"
+#include "NativeWlcManager.h"
#include "NfcAdaptation.h"
#ifdef DTA_ENABLED
#include "NfcDta.h"
@@ -96,10 +97,9 @@
jmethodID gCachedNfcManagerNotifyEeUpdated;
jmethodID gCachedNfcManagerNotifyHwErrorReported;
jmethodID gCachedNfcManagerNotifyPollingLoopFrame;
+jmethodID gCachedNfcManagerNotifyWlcStopped;
jmethodID gCachedNfcManagerNotifyVendorSpecificEvent;
jmethodID gCachedNfcManagerNotifyCommandTimeout;
-const char* gNativeP2pDeviceClassName =
- "com/android/nfc/dhimpl/NativeP2pDevice";
const char* gNativeNfcTagClassName = "com/android/nfc/dhimpl/NativeNfcTag";
const char* gNativeNfcManagerClassName =
"com/android/nfc/dhimpl/NativeNfcManager";
@@ -138,7 +138,9 @@
static jint sLfT3tMax = 0;
static bool sRoutingInitialized = false;
static bool sIsRecovering = false;
+static bool sIsAlwaysPolling = false;
static std::vector<uint8_t> sRawVendorCmdResponse;
+static bool sEnableVendorNciNotifications = false;
#define CONFIG_UPDATE_TECH_MASK (1 << 1)
#define DEFAULT_TECH_MASK \
@@ -157,17 +159,21 @@
static tNFA_STATUS startPolling_rfDiscoveryDisabled(
tNFA_TECHNOLOGY_MASK tech_mask);
static void nfcManager_doSetScreenState(JNIEnv* e, jobject o,
- jint screen_state_mask);
+ jint screen_state_mask,
+ jboolean alwaysPoll);
static jboolean nfcManager_doSetPowerSavingMode(JNIEnv* e, jobject o,
bool flag);
static void sendRawVsCmdCallback(uint8_t event, uint16_t param_len,
uint8_t* p_param);
+static jbyteArray nfcManager_getProprietaryCaps(JNIEnv* e, jobject o);
tNFA_STATUS gVSCmdStatus = NFA_STATUS_OK;
uint16_t gCurrentConfigLen;
uint8_t gConfig[256];
+std::vector<uint8_t> gCaps(0);
static int prevScreenState = NFA_SCREEN_STATE_OFF_LOCKED;
static int NFA_SCREEN_POLLING_TAG_MASK = 0x10;
static bool gIsDtaEnabled = false;
+static bool gObserveModeEnabled = false;
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
@@ -175,7 +181,7 @@
void initializeGlobalDebugEnabledFlag() {
bool nfc_debug_enabled =
(NfcConfig::getUnsigned(NAME_NFC_DEBUG_ENABLED, 1) != 0) ||
- property_get_bool("persist.nfc.debug_enabled", false);
+ property_get_bool("persist.nfc.debug_enabled", true);
android::base::SetMinimumLogSeverity(nfc_debug_enabled ? android::base::DEBUG
: android::base::INFO);
@@ -380,7 +386,9 @@
if (!isListenMode(eventData->activated) &&
(prevScreenState == NFA_SCREEN_STATE_OFF_LOCKED ||
prevScreenState == NFA_SCREEN_STATE_OFF_UNLOCKED)) {
- NFA_Deactivate(FALSE);
+ if (!sIsAlwaysPolling) {
+ NFA_Deactivate(FALSE);
+ }
}
NfcTag::getInstance().connectionEventHandler(connEvent, eventData);
@@ -606,9 +614,13 @@
gCachedNfcManagerNotifyPollingLoopFrame =
e->GetMethodID(cls.get(), "notifyPollingLoopFrame", "(I[B)V");
+
gCachedNfcManagerNotifyVendorSpecificEvent =
e->GetMethodID(cls.get(), "notifyVendorSpecificEvent", "(II[B)V");
+ gCachedNfcManagerNotifyWlcStopped =
+ e->GetMethodID(cls.get(), "notifyWlcStopped", "(I)V");
+
gCachedNfcManagerNotifyCommandTimeout =
e->GetMethodID(cls.get(), "notifyCommandTimeout", "()V");
@@ -618,12 +630,6 @@
return JNI_FALSE;
}
- if (nfc_jni_cache_object(e, gNativeP2pDeviceClassName,
- &(nat->cached_P2pDevice)) == -1) {
- LOG(ERROR) << StringPrintf("%s: fail cache NativeP2pDevice", __func__);
- return JNI_FALSE;
- }
-
LOG(DEBUG) << StringPrintf("%s: exit", __func__);
return JNI_TRUE;
}
@@ -723,13 +729,13 @@
__func__);
struct nfc_jni_native_data* nat = getNative(NULL, NULL);
- JNIEnv* e = NULL;
- ScopedAttach attach(nat->vm, &e);
- if (e == NULL) {
- LOG(ERROR) << StringPrintf("jni env is null");
- return;
- }
if (recovery_option && nat != NULL) {
+ JNIEnv* e = NULL;
+ ScopedAttach attach(nat->vm, &e);
+ if (e == NULL) {
+ LOG(ERROR) << StringPrintf("jni env is null");
+ return;
+ }
LOG(ERROR) << StringPrintf("%s: toggle NFC state to recovery nfc",
__func__);
sIsRecovering = true;
@@ -804,8 +810,14 @@
}
PowerSwitch::getInstance().initialize(PowerSwitch::UNKNOWN_LEVEL);
LOG(ERROR) << StringPrintf("%s: crash NFC service", __func__);
- e->CallVoidMethod(nat->manager,
- android::gCachedNfcManagerNotifyCommandTimeout);
+ if (nat != NULL) {
+ JNIEnv* e = NULL;
+ ScopedAttach attach(nat->vm, &e);
+ if (e != NULL) {
+ e->CallVoidMethod(nat->manager,
+ android::gCachedNfcManagerNotifyCommandTimeout);
+ }
+ }
//////////////////////////////////////////////
// crash the NFC service process so it can restart automatically
abort();
@@ -940,6 +952,27 @@
case NCI_MSG_PROP_ANDROID: {
uint8_t android_sub_opcode = p_param[3];
switch (android_sub_opcode) {
+ case NCI_QUERY_ANDROID_PASSIVE_OBSERVE: {
+ gObserveModeEnabled = p_param[5];
+ LOG(INFO) << StringPrintf("Query Observe mode state is %s",
+ gObserveModeEnabled ? "TRUE" : "FALSE");
+ }
+ FALLTHROUGH_INTENDED;
+ case NCI_ANDROID_PASSIVE_OBSERVE: {
+ gVSCmdStatus = p_param[4];
+ LOG(INFO) << StringPrintf("Observe mode RSP: status: %x",
+ gVSCmdStatus);
+ SyncEventGuard guard(gNfaVsCommand);
+ gNfaVsCommand.notifyOne();
+ } break;
+ case NCI_ANDROID_GET_CAPS: {
+ gVSCmdStatus = p_param[4];
+ SyncEventGuard guard(gNfaVsCommand);
+ u_int16_t android_version = *(u_int16_t*)&p_param[5];
+ u_int8_t len = p_param[7];
+ gCaps.assign(p_param + 8, p_param + 8 + len);
+ gNfaVsCommand.notifyOne();
+ } break;
case NCI_ANDROID_POLLING_FRAME_NTF: {
struct nfc_jni_native_data* nat = getNative(NULL, NULL);
if (!nat) {
@@ -975,34 +1008,73 @@
}
} break;
default: {
- struct nfc_jni_native_data* nat = getNative(NULL, NULL);
- if (!nat) {
- LOG(ERROR) << StringPrintf("%s: cached nat is null", __FUNCTION__);
- return;
+ if (sEnableVendorNciNotifications) {
+ struct nfc_jni_native_data* nat = getNative(NULL, NULL);
+ if (!nat) {
+ LOG(ERROR) << StringPrintf("%s: cached nat is null", __FUNCTION__);
+ return;
+ }
+ JNIEnv* e = NULL;
+ ScopedAttach attach(nat->vm, &e);
+ if (e == NULL) {
+ LOG(ERROR) << StringPrintf("%s: jni env is null", __FUNCTION__);
+ return;
+ }
+ ScopedLocalRef<jobject> dataJavaArray(e, e->NewByteArray(param_len));
+ if (dataJavaArray.get() == NULL) {
+ LOG(ERROR) << StringPrintf("%s: fail allocate array", __FUNCTION__);
+ return;
+ }
+ e->SetByteArrayRegion((jbyteArray)dataJavaArray.get(), 0, param_len,
+ (jbyte*)(p_param));
+ if (e->ExceptionCheck()) {
+ e->ExceptionClear();
+ LOG(ERROR) << StringPrintf("%s failed to fill array", __FUNCTION__);
+ return;
+ }
+ e->CallVoidMethod(nat->manager,
+ android::gCachedNfcManagerNotifyVendorSpecificEvent,
+ (jint)event, (jint)param_len, dataJavaArray.get());
}
- JNIEnv* e = NULL;
- ScopedAttach attach(nat->vm, &e);
- if (e == NULL) {
- LOG(ERROR) << StringPrintf("%s: jni env is null", __FUNCTION__);
- return;
- }
- ScopedLocalRef<jobject> dataJavaArray(e, e->NewByteArray(param_len));
- if (dataJavaArray.get() == NULL) {
- LOG(ERROR) << StringPrintf("%s: fail allocate array", __FUNCTION__);
- return;
- }
- e->SetByteArrayRegion((jbyteArray)dataJavaArray.get(), 0, param_len, (jbyte*)(p_param));
- if (e->ExceptionCheck()) {
- e->ExceptionClear();
- LOG(ERROR) << StringPrintf("%s failed to fill array", __FUNCTION__);
- return;
- }
- e->CallVoidMethod(nat->manager, android::gCachedNfcManagerNotifyVendorSpecificEvent,
- (jint)event, (jint)param_len, dataJavaArray.get());
} break;
}
}
+static jboolean isObserveModeSupported(JNIEnv* e, jobject o) {
+ ScopedLocalRef<jclass> cls(e, e->GetObjectClass(o));
+ jmethodID isSupported =
+ e->GetMethodID(cls.get(), "isObserveModeSupported", "()Z");
+ return e->CallBooleanMethod(o, isSupported);
+}
+
+static jboolean nfcManager_isObserveModeEnabled(JNIEnv* e, jobject o) {
+ if (isObserveModeSupported(e, o) == JNI_FALSE) {
+ return false;
+ }
+
+ uint8_t cmd[] = {(NCI_MT_CMD << NCI_MT_SHIFT) | NCI_GID_PROP,
+ NCI_MSG_PROP_ANDROID,
+ NCI_QUERY_ANDROID_PASSIVE_OBSERVE_PARAM_SIZE,
+ NCI_QUERY_ANDROID_PASSIVE_OBSERVE};
+ SyncEventGuard guard(gNfaVsCommand);
+ tNFA_STATUS status = NFA_SendRawVsCommand(sizeof(cmd), cmd, nfaVSCallback);
+
+ if (status == NFA_STATUS_OK) {
+ if (!gNfaVsCommand.wait(1000)) {
+ LOG(ERROR) << StringPrintf(
+ "%s: Timed out waiting for a response to get observe mode ",
+ __FUNCTION__);
+ gVSCmdStatus = NFA_STATUS_FAILED;
+ }
+ } else {
+ LOG(DEBUG) << StringPrintf("%s: Failed to get observe mode ", __FUNCTION__);
+ }
+ LOG(DEBUG) << StringPrintf(
+ "%s: returning %s", __FUNCTION__,
+ (gObserveModeEnabled != JNI_FALSE ? "TRUE" : "FALSE"));
+ return gObserveModeEnabled;
+}
+
static void nfaSendRawVsCmdCallback(uint8_t event, uint16_t param_len,
uint8_t* p_param) {
if (param_len == 5) {
@@ -1014,7 +1086,21 @@
gNfaVsCommand.notifyOne();
}
-static jboolean nfcManager_setObserveMode(JNIEnv* e, jobject, jboolean enable) {
+static jboolean nfcManager_setObserveMode(JNIEnv* e, jobject o,
+ jboolean enable) {
+ if (isObserveModeSupported(e, o) == JNI_FALSE) {
+ return false;
+ }
+
+ if ((gObserveModeEnabled == enable) &&
+ ((enable != JNI_FALSE) ==
+ (nfcManager_isObserveModeEnabled(e, o) != JNI_FALSE))) {
+ LOG(DEBUG) << StringPrintf(
+ "%s: called with %s but it is already %s, returning early",
+ __FUNCTION__, (enable != JNI_FALSE ? "TRUE" : "FALSE"),
+ (gObserveModeEnabled != JNI_FALSE ? "TRUE" : "FALSE"));
+ return true;
+ }
bool reenbleDiscovery = false;
if (sRfEnabled) {
startRfDiscovery(false);
@@ -1022,24 +1108,42 @@
}
uint8_t cmd[] = {
(NCI_MT_CMD << NCI_MT_SHIFT) | NCI_GID_PROP, NCI_MSG_PROP_ANDROID,
- NCI_ANDROID_PASSIVE_OBSERVER_PARAM_SIZE, NCI_ANDROID_PASSIVE_OBSERVER,
+ NCI_ANDROID_PASSIVE_OBSERVE_PARAM_SIZE, NCI_ANDROID_PASSIVE_OBSERVE,
static_cast<uint8_t>(enable != JNI_FALSE
- ? NCI_ANDROID_PASSIVE_OBSERVER_PARAM_ENABLE
- : NCI_ANDROID_PASSIVE_OBSERVER_PARAM_DISABLE)};
+ ? NCI_ANDROID_PASSIVE_OBSERVE_PARAM_ENABLE
+ : NCI_ANDROID_PASSIVE_OBSERVE_PARAM_DISABLE)};
+ {
+ SyncEventGuard guard(gNfaVsCommand);
+ tNFA_STATUS status = NFA_SendRawVsCommand(sizeof(cmd), cmd, nfaVSCallback);
- tNFA_STATUS status =
- NFA_SendRawVsCommand(sizeof(cmd), cmd, nfaSendRawVsCmdCallback);
-
- if (status == NFA_STATUS_OK) {
- gNfaVsCommand.wait();
- } else {
- LOG(DEBUG) << StringPrintf("%s: Failed to set observe mode ", __FUNCTION__);
- gVSCmdStatus = NFA_STATUS_FAILED;
+ if (status == NFA_STATUS_OK) {
+ if (!gNfaVsCommand.wait(1000)) {
+ LOG(ERROR) << StringPrintf(
+ "%s: Timed out waiting for a response to set observe mode ",
+ __FUNCTION__);
+ gVSCmdStatus = NFA_STATUS_FAILED;
+ }
+ } else {
+ LOG(DEBUG) << StringPrintf("%s: Failed to set observe mode ",
+ __FUNCTION__);
+ gVSCmdStatus = NFA_STATUS_FAILED;
+ }
}
if (reenbleDiscovery) {
startRfDiscovery(true);
}
- return gVSCmdStatus == NFA_STATUS_OK;
+
+ if (gVSCmdStatus == NFA_STATUS_OK) {
+ gObserveModeEnabled = enable;
+ } else {
+ gObserveModeEnabled = nfcManager_isObserveModeEnabled(e, o);
+ }
+
+ LOG(DEBUG) << StringPrintf(
+ "%s: Set observe mode to %s with result %x, observe mode is now %s.",
+ __FUNCTION__, (enable != JNI_FALSE ? "TRUE" : "FALSE"), gVSCmdStatus,
+ (gObserveModeEnabled ? "enabled" : "disabled"));
+ return gObserveModeEnabled == enable;
}
/*******************************************************************************
@@ -1163,6 +1267,7 @@
nativeNfcTag_registerNdefTypeHandler();
NfcTag::getInstance().initialize(getNative(e, o));
HciEventManager::getInstance().initialize(getNative(e, o));
+ NativeWlcManager::getInstance().initialize(getNative(e, o));
/////////////////////////////////////////////////////////////////////////////////
// Add extra configuration here (work-arounds, etc.)
@@ -1622,11 +1727,13 @@
}
static void nfcManager_doSetScreenState(JNIEnv* e, jobject o,
- jint screen_state_mask) {
+ jint screen_state_mask,
+ jboolean alwaysPoll) {
tNFA_STATUS status = NFA_STATUS_OK;
uint8_t state = (screen_state_mask & NFA_SCREEN_STATE_MASK);
uint8_t discovry_param =
NCI_LISTEN_DH_NFCEE_ENABLE_MASK | NCI_POLLING_DH_ENABLE_MASK;
+ sIsAlwaysPolling = alwaysPoll;
LOG(DEBUG) << StringPrintf(
"%s: state = %d prevScreenState= %d, discovry_param = %d", __FUNCTION__,
@@ -1691,17 +1798,18 @@
NCI_LISTEN_DH_NFCEE_ENABLE_MASK | NCI_POLLING_DH_ENABLE_MASK;
}
- SyncEventGuard guard(gNfaSetConfigEvent);
- status = NFA_SetConfig(NCI_PARAM_ID_CON_DISCOVERY_PARAM,
- NCI_PARAM_LEN_CON_DISCOVERY_PARAM, &discovry_param);
- if (status == NFA_STATUS_OK) {
- gNfaSetConfigEvent.wait();
- } else {
- LOG(ERROR) << StringPrintf("%s: Failed to update CON_DISCOVER_PARAM",
- __FUNCTION__);
- return;
+ if (!sIsAlwaysPolling) {
+ SyncEventGuard guard(gNfaSetConfigEvent);
+ status = NFA_SetConfig(NCI_PARAM_ID_CON_DISCOVERY_PARAM,
+ NCI_PARAM_LEN_CON_DISCOVERY_PARAM, &discovry_param);
+ if (status == NFA_STATUS_OK) {
+ gNfaSetConfigEvent.wait();
+ } else {
+ LOG(ERROR) << StringPrintf("%s: Failed to update CON_DISCOVER_PARAM",
+ __FUNCTION__);
+ return;
+ }
}
-
// skip remaining SetScreenState tasks when trying to silent recover NFCC
if (recovery_option && sIsRecovering) {
prevScreenState = state;
@@ -1737,16 +1845,6 @@
prevScreenState = state;
}
-static void nfcManager_doEnableScreenOffSuspend(JNIEnv* e, jobject o) {
- PowerSwitch::getInstance().setScreenOffPowerState(
- PowerSwitch::POWER_STATE_FULL);
-}
-
-static void nfcManager_doDisableScreenOffSuspend(JNIEnv* e, jobject o) {
- PowerSwitch::getInstance().setScreenOffPowerState(
- PowerSwitch::POWER_STATE_OFF);
-}
-
/*******************************************************************************
**
** Function: nfcManager_getIsoDepMaxTransceiveLength
@@ -1781,6 +1879,26 @@
/*******************************************************************************
**
+** Function: nfcManager_IsMultiTag
+**
+** Description: Check if it a multi tag case.
+** e: JVM environment.
+** o: Java object.
+**
+** Returns: None.
+**
+*******************************************************************************/
+static bool nfcManager_isMultiTag() {
+ LOG(DEBUG) << StringPrintf("%s: enter mNumRfDiscId = %d", __func__,
+ NfcTag::getInstance().mNumRfDiscId);
+ bool status = false;
+ if (NfcTag::getInstance().mNumRfDiscId > 1) status = true;
+ LOG(DEBUG) << StringPrintf("isMultiTag = %d", status);
+ return status;
+}
+
+/*******************************************************************************
+**
** Function: nfcManager_doStartStopPolling
**
** Description: Start or stop NFC RF polling
@@ -1955,6 +2073,13 @@
}
nativeNfcTag_releaseRfInterfaceMutexLock();
}
+
+static void ncfManager_nativeEnableVendorNciNotifications(JNIEnv* env,
+ jobject o,
+ jboolean enable) {
+ sEnableVendorNciNotifications = (enable == JNI_TRUE);
+}
+
static jobject nfcManager_nativeSendRawVendorCmd(JNIEnv* env, jobject o,
jint mt, jint gid, jint oid,
jbyteArray payload) {
@@ -2070,13 +2195,7 @@
{"doAbort", "(Ljava/lang/String;)V", (void*)nfcManager_doAbort},
- {"doEnableScreenOffSuspend", "()V",
- (void*)nfcManager_doEnableScreenOffSuspend},
-
- {"doSetScreenState", "(I)V", (void*)nfcManager_doSetScreenState},
-
- {"doDisableScreenOffSuspend", "()V",
- (void*)nfcManager_doDisableScreenOffSuspend},
+ {"doSetScreenState", "(IZ)V", (void*)nfcManager_doSetScreenState},
{"doDump", "(Ljava/io/FileDescriptor;)V", (void*)nfcManager_doDump},
@@ -2105,6 +2224,10 @@
{"setObserveMode", "(Z)Z", (void*)nfcManager_setObserveMode},
+ {"isObserveModeEnabled", "()Z", (void*)nfcManager_isObserveModeEnabled},
+
+ {"isMultiTag", "()Z", (void*)nfcManager_isMultiTag},
+
{"clearRoutingEntry", "(I)V", (void*)nfcManager_clearRoutingEntry},
{"setIsoDepProtocolRoute", "(I)V",
@@ -2117,6 +2240,10 @@
{"resetDiscoveryTech", "()V", (void*)nfcManager_resetDiscoveryTech},
{"nativeSendRawVendorCmd", "(III[B)Lcom/android/nfc/NfcVendorNciResponse;",
(void*)nfcManager_nativeSendRawVendorCmd},
+
+ {"getProprietaryCaps", "()[B", (void*)nfcManager_getProprietaryCaps},
+ {"enableVendorNciNotifications", "(Z)V",
+ (void*)ncfManager_nativeEnableVendorNciNotifications},
};
/*******************************************************************************
@@ -2331,4 +2458,26 @@
return gVSCmdStatus == NFA_STATUS_OK;
}
+static jbyteArray nfcManager_getProprietaryCaps(JNIEnv* e, jobject o) {
+ LOG(DEBUG) << StringPrintf("%s: enter; ", __func__);
+ uint8_t cmd[] = {(NCI_MT_CMD << NCI_MT_SHIFT) | NCI_GID_PROP,
+ NCI_MSG_PROP_ANDROID, NCI_ANDROID_GET_CAPS_PARAM_SIZE,
+ NCI_ANDROID_GET_CAPS};
+ SyncEventGuard guard(gNfaVsCommand);
+
+ tNFA_STATUS status =
+ NFA_SendRawVsCommand(sizeof(cmd), cmd, nfaSendRawVsCmdCallback);
+ if (status == NFA_STATUS_OK) {
+ gNfaVsCommand.wait();
+ } else {
+ LOG(ERROR) << StringPrintf("%s: Failed to get caps", __func__);
+ gVSCmdStatus = NFA_STATUS_FAILED;
+ }
+ CHECK(e);
+ jbyteArray rtJavaArray = e->NewByteArray(gCaps.size());
+ CHECK(rtJavaArray);
+ e->SetByteArrayRegion(rtJavaArray, 0, gCaps.size(), (jbyte*)gCaps.data());
+ return rtJavaArray;
+}
+
} /* namespace android */
diff --git a/nci/jni/NativeP2pDevice.cpp b/nci/jni/NativeP2pDevice.cpp
deleted file mode 100644
index efb8252..0000000
--- a/nci/jni/NativeP2pDevice.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <nativehelper/JNIHelp.h>
-
-#include "JavaClassConstants.h"
-#include "NfcJniUtil.h"
-
-using android::base::StringPrintf;
-
-namespace android {
-
-static jboolean nativeP2pDeviceDoConnect(JNIEnv*, jobject) {
- LOG(DEBUG) << StringPrintf("%s", __func__);
- return JNI_TRUE;
-}
-
-static jboolean nativeP2pDeviceDoDisconnect(JNIEnv*, jobject) {
- LOG(DEBUG) << StringPrintf("%s", __func__);
- return JNI_TRUE;
-}
-
-static jbyteArray nativeP2pDeviceDoTransceive(JNIEnv*, jobject, jbyteArray) {
- LOG(DEBUG) << StringPrintf("%s", __func__);
- return NULL;
-}
-
-static jbyteArray nativeP2pDeviceDoReceive(JNIEnv*, jobject) {
- LOG(DEBUG) << StringPrintf("%s", __func__);
- return NULL;
-}
-
-static jboolean nativeP2pDeviceDoSend(JNIEnv*, jobject, jbyteArray) {
- LOG(DEBUG) << StringPrintf("%s", __func__);
- return JNI_TRUE;
-}
-
-/*****************************************************************************
-**
-** Description: JNI functions
-**
-*****************************************************************************/
-static JNINativeMethod gMethods[] = {
- {"doConnect", "()Z", (void*)nativeP2pDeviceDoConnect},
- {"doDisconnect", "()Z", (void*)nativeP2pDeviceDoDisconnect},
- {"doTransceive", "([B)[B", (void*)nativeP2pDeviceDoTransceive},
- {"doReceive", "()[B", (void*)nativeP2pDeviceDoReceive},
- {"doSend", "([B)Z", (void*)nativeP2pDeviceDoSend},
-};
-
-/*******************************************************************************
-**
-** Function: register_com_android_nfc_NativeP2pDevice
-**
-** Description: Regisgter JNI functions with Java Virtual Machine.
-** e: Environment of JVM.
-**
-** Returns: Status of registration.
-**
-*******************************************************************************/
-int register_com_android_nfc_NativeP2pDevice(JNIEnv* e) {
- return jniRegisterNativeMethods(e, gNativeP2pDeviceClassName, gMethods,
- NELEM(gMethods));
-}
-
-} // namespace android
diff --git a/nci/jni/NativeWlcManager.cpp b/nci/jni/NativeWlcManager.cpp
new file mode 100644
index 0000000..eb5e140
--- /dev/null
+++ b/nci/jni/NativeWlcManager.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#include "NativeWlcManager.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <errno.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <semaphore.h>
+
+#include "JavaClassConstants.h"
+#include "NfcJniUtil.h"
+#include "SyncEvent.h"
+#include "nfa_api.h"
+
+using android::base::StringPrintf;
+
+/*****************************************************************************
+**
+** private variables and functions
+**
+*****************************************************************************/
+
+static void nfaWlcManagementCallback(tNFA_WLC_EVT wlcEvent,
+ tNFA_WLC_EVT_DATA* eventData);
+
+static SyncEvent sNfaWlcEnableEvent; // event for NFA_WlcStart()
+static SyncEvent sNfaWlcEvent; // event for NFA_Wlc...()
+
+static bool sIsWlcpStarted = false;
+
+Mutex gMutexWlc;
+
+const JNINativeMethod NativeWlcManager::sMethods[] = {
+
+ {"startWlcPowerTransfer", "(II)Z",
+ (void*)NativeWlcManager::com_android_nfc_wlc_chargeWlcListener},
+ {"enableWlc", "(I)Z",
+ (void*)NativeWlcManager::com_android_nfc_wlc_startWlcP},
+
+};
+
+/*******************************************************************************
+**
+** Function: NativeWlcManager
+**
+** Description: Initialize member variables.
+**
+** Returns: None
+**
+*******************************************************************************/
+NativeWlcManager::NativeWlcManager()
+ : mNativeData(NULL), mIsWlcEnabled(false) {}
+
+/*******************************************************************************
+**
+** Function: ~NativeWlcManager
+**
+** Description: Release all resources.
+**
+** Returns: None
+**
+*******************************************************************************/
+NativeWlcManager::~NativeWlcManager() {}
+
+/*******************************************************************************
+**
+** Function: getInstance
+**
+** Description: Get a reference to the singleton NativeWlcManager object.
+**
+** Returns: Reference to NativeWlcManager object.
+**
+*******************************************************************************/
+NativeWlcManager& NativeWlcManager::getInstance() {
+ static NativeWlcManager manager;
+ return manager;
+}
+
+/*******************************************************************************
+**
+** Function: initialize
+**
+** Description: Reset member variables.
+** native: Native data.
+**
+** Returns: None
+**
+*******************************************************************************/
+void NativeWlcManager::initialize(nfc_jni_native_data* native) {
+ tNFA_STATUS stat = NFA_STATUS_FAILED;
+
+ LOG(DEBUG) << StringPrintf("%s: enter", __func__);
+
+ mNativeData = native;
+ mIsWlcEnabled = false;
+
+ SyncEventGuard g(sNfaWlcEnableEvent);
+ // TODO: only do it once at NfcManager init if WLC allowed
+ stat = NFA_WlcEnable(nfaWlcManagementCallback);
+
+ if (stat == NFA_STATUS_OK) {
+ // TODO: get enable result to stop directly if failed
+ sNfaWlcEnableEvent.wait();
+ LOG(DEBUG) << StringPrintf("%s: enable Wlc module success", __func__);
+ } else {
+ LOG(ERROR) << StringPrintf("%s: fail enable Wlc module; error=0x%X",
+ __func__, stat);
+ }
+}
+
+/*******************************************************************************
+**
+** Function: notifyWlcCompletion
+**
+** Description: Notify end of WLC procedure.
+** wpt_end_condition: End condition from NFCC.
+**
+** Returns: None
+**
+*******************************************************************************/
+void NativeWlcManager::notifyWlcCompletion(uint8_t wpt_end_condition) {
+ JNIEnv* e = NULL;
+ ScopedAttach attach(mNativeData->vm, &e);
+ if (e == NULL) {
+ LOG(ERROR) << "jni env is null";
+ return;
+ }
+
+ LOG(DEBUG) << StringPrintf("%s: ", __func__);
+
+ e->CallVoidMethod(mNativeData->manager,
+ android::gCachedNfcManagerNotifyWlcStopped,
+ (int)wpt_end_condition);
+ if (e->ExceptionCheck()) {
+ e->ExceptionClear();
+ LOG(ERROR) << StringPrintf("fail notify");
+ }
+}
+
+/*******************************************************************************
+**
+** Function: nfaWlcManagementCallback
+**
+** Description: Receive Wlc management events from stack.
+** wlcEvent: Wlc-management event ID.
+** eventData: Data associated with event ID.
+**
+** Returns: None
+**
+*******************************************************************************/
+void nfaWlcManagementCallback(tNFA_WLC_EVT wlcEvent,
+ tNFA_WLC_EVT_DATA* eventData) {
+ LOG(DEBUG) << StringPrintf("%s: enter; event=0x%X", __func__, wlcEvent);
+
+ switch (wlcEvent) {
+ case NFA_WLC_ENABLE_RESULT_EVT: // whether WLC module enabled
+ {
+ LOG(DEBUG) << StringPrintf("%s: NFA_WLC_ENABLE_RESULT_EVT: status = %u",
+ __func__, eventData->status);
+
+ SyncEventGuard guard(sNfaWlcEnableEvent);
+ sNfaWlcEnableEvent.notifyOne();
+ } break;
+
+ case NFA_WLC_START_RESULT_EVT: // whether WLCP successfully started
+ {
+ LOG(DEBUG) << StringPrintf("%s: NFA_WLC_START_RESULT_EVT: status = %u",
+ __func__, eventData->status);
+
+ sIsWlcpStarted = eventData->status == NFA_STATUS_OK;
+ SyncEventGuard guard(sNfaWlcEvent);
+ sNfaWlcEvent.notifyOne();
+ } break;
+
+ case NFA_WLC_START_WPT_RESULT_EVT: // whether WLC Power Transfer
+ // successfully started
+ {
+ LOG(DEBUG) << StringPrintf(
+ "%s: NFA_WLC_START_WPT_RESULT_EVT: status = %u", __func__,
+ eventData->status);
+
+ SyncEventGuard guard(sNfaWlcEvent);
+ sNfaWlcEvent.notifyOne();
+ } break;
+
+ case NFA_WLC_CHARGING_RESULT_EVT: // notify completion of power transfer
+ // phase
+ {
+ LOG(DEBUG) << StringPrintf(
+ "%s: NFA_WLC_CHARGING_RESULT_EVT: End Condition = 0x%x", __func__,
+ eventData->wpt_end_cdt);
+
+ /* Return WPT end condition to service */
+ NativeWlcManager::getInstance().notifyWlcCompletion(
+ eventData->wpt_end_cdt);
+ } break;
+
+ default:
+ LOG(DEBUG) << StringPrintf("%s: unhandled event", __func__);
+ break;
+ }
+}
+
+/*******************************************************************************
+**
+** Function: com_android_nfc_wlc_startWlcP
+**
+** Description: Start WLC Poller
+** e: JVM environment.
+** mode: WLC mode
+**
+** Returns: True if WLCP started done
+**
+*******************************************************************************/
+jboolean NativeWlcManager::com_android_nfc_wlc_startWlcP(JNIEnv* e, jobject,
+ jint mode) {
+ tNFA_STATUS stat = NFA_STATUS_FAILED;
+
+ LOG(DEBUG) << StringPrintf("%s: enter", __func__);
+
+ gMutexWlc.lock();
+ SyncEventGuard g(sNfaWlcEvent);
+ stat = NFA_WlcStart(mode);
+
+ if (stat == NFA_STATUS_OK) {
+ LOG(DEBUG) << StringPrintf(
+ "%s: start Wlc Poller, wait for success confirmation", __func__);
+ sNfaWlcEvent.wait();
+ } else {
+ LOG(ERROR) << StringPrintf("%s: fail start WlcPoller; error=0x%X", __func__,
+ stat);
+ }
+ gMutexWlc.unlock();
+ return sIsWlcpStarted ? JNI_TRUE : JNI_FALSE;
+}
+
+/*******************************************************************************
+**
+** Function: com_android_nfc_wlc_chargeWlcListener
+**
+** Description: Start charging WLC Listener
+** e: JVM environment.
+** power_adj_req:
+** wpt_time_int:
+**
+** Returns: True if WLCL charging started properly
+**
+*******************************************************************************/
+jboolean NativeWlcManager::com_android_nfc_wlc_chargeWlcListener(
+ JNIEnv* e, jobject, jint power_adj_req, jint wpt_time_int) {
+ tNFA_STATUS stat = NFA_STATUS_FAILED;
+
+ LOG(DEBUG) << StringPrintf("%s: wpt_time_int = %d", __func__, wpt_time_int);
+
+ gMutexWlc.lock();
+ SyncEventGuard g(sNfaWlcEvent);
+ // TODO: condition call to sIsWlcpStarted
+ // TODO: limit the min of wpt_time_int
+ stat = NFA_WlcStartWPT((uint16_t)(power_adj_req & 0xFFFF), wpt_time_int);
+ if (stat == NFA_STATUS_OK) {
+ LOG(DEBUG) << StringPrintf(
+ "%s: charge Wlc Listener, wait for success confirmation", __func__);
+ sNfaWlcEvent.wait();
+ } else {
+ LOG(ERROR) << StringPrintf("%s: fail charge Wlc Listener; error=0x%X",
+ __func__, stat);
+ gMutexWlc.unlock();
+ return false;
+ }
+ gMutexWlc.unlock();
+ return true;
+}
+
+/*******************************************************************************
+**
+** Function: registerJniFunctions
+**
+** Description: Register WLC feature JNI functions
+** e: JVM environment.
+**
+** Returns: -1 if JNI register error
+**
+*******************************************************************************/
+int NativeWlcManager::registerJniFunctions(JNIEnv* e) {
+ static const char fn[] = "NativeWlcManager::registerJniFunctions";
+ LOG(DEBUG) << StringPrintf("%s", fn);
+ return jniRegisterNativeMethods(e, "com/android/nfc/wlc/NfcCharging",
+ sMethods, NELEM(sMethods));
+}
diff --git a/nci/jni/NativeWlcManager.h b/nci/jni/NativeWlcManager.h
new file mode 100644
index 0000000..57897c9
--- /dev/null
+++ b/nci/jni/NativeWlcManager.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+#include "NfcJniUtil.h"
+#include "nfa_wlc_api.h"
+
+/*****************************************************************************
+**
+** Name: NativeWlcManager
+**
+** Description: Manage Wlc activities at stack level.
+**
+*****************************************************************************/
+class NativeWlcManager {
+ public:
+ /*******************************************************************************
+ **
+ ** Function: getInstance
+ **
+ ** Description: Get the singleton of this object.
+ **
+ ** Returns: Reference to this object.
+ **
+ *******************************************************************************/
+ static NativeWlcManager& getInstance();
+
+ /*******************************************************************************
+ **
+ ** Function: initialize
+ **
+ ** Description: Reset member variables.
+ ** native: Native data.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ void initialize(nfc_jni_native_data* native);
+
+ /*******************************************************************************
+ **
+ ** Function: registerJniFunctions
+ **
+ ** Description: Register WLC JNI functions.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ int registerJniFunctions(JNIEnv* e);
+
+ /*******************************************************************************
+ **
+ ** Function: notifyWlcCompletion
+ **
+ ** Description: Notify end of WLC procedure.
+ ** wpt_end_condition: End condition from NFCC.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ void notifyWlcCompletion(uint8_t wpt_end_condition);
+
+ private:
+ // Fields below are final after initialize()
+ nfc_jni_native_data* mNativeData;
+
+ bool mIsWlcEnabled = false;
+
+ /*******************************************************************************
+ **
+ ** Function: NativeWlcManager
+ **
+ ** Description: Initialize member variables.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ NativeWlcManager();
+
+ /*******************************************************************************
+ **
+ ** Function: ~NativeWlcManager
+ **
+ ** Description: Release all resources.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ ~NativeWlcManager();
+
+ /*******************************************************************************
+ **
+ ** Function: wlcManagementCallback
+ **
+ ** Description: Callback function for the stack.
+ ** event: event ID.
+ ** eventData: event's data.
+ **
+ ** Returns: None
+ **
+ *******************************************************************************/
+ static void wlcManagementCallback(tNFA_WLC_EVT wlcEvent,
+ tNFA_WLC_EVT_DATA* eventData);
+
+ static const JNINativeMethod sMethods[];
+
+ static jboolean com_android_nfc_wlc_startWlcP(JNIEnv* e, jobject, jint mode);
+ static jboolean com_android_nfc_wlc_chargeWlcListener(JNIEnv* e, jobject,
+ jint power_adj_req,
+ jint wpt_time_int);
+};
diff --git a/nci/jni/NfcJniUtil.cpp b/nci/jni/NfcJniUtil.cpp
index 10e1b5e..dbba6c5 100644
--- a/nci/jni/NfcJniUtil.cpp
+++ b/nci/jni/NfcJniUtil.cpp
@@ -23,6 +23,7 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
+#include "NativeWlcManager.h"
#include "RoutingManager.h"
using android::base::StringPrintf;
@@ -52,7 +53,8 @@
if (android::register_com_android_nfc_NativeNfcTag(e) == -1) return JNI_ERR;
if (RoutingManager::getInstance().registerJniFunctions(e) == -1)
return JNI_ERR;
-
+ if (NativeWlcManager::getInstance().registerJniFunctions(e) == -1)
+ return JNI_ERR;
LOG(DEBUG) << StringPrintf("%s: exit", __func__);
return JNI_VERSION_1_6;
}
diff --git a/nci/jni/NfcJniUtil.h b/nci/jni/NfcJniUtil.h
index 6e09c7f..b1bedfa 100644
--- a/nci/jni/NfcJniUtil.h
+++ b/nci/jni/NfcJniUtil.h
@@ -105,7 +105,6 @@
/* Cached objects */
jobject cached_NfcTag;
- jobject cached_P2pDevice;
/* Secure Element selected */
int seId;
@@ -143,5 +142,4 @@
struct nfc_jni_native_data* nfc_jni_get_nat(JNIEnv* e, jobject o);
int register_com_android_nfc_NativeNfcManager(JNIEnv* e);
int register_com_android_nfc_NativeNfcTag(JNIEnv* e);
-int register_com_android_nfc_NativeP2pDevice(JNIEnv* e);
} // namespace android
diff --git a/nci/jni/NfcTag.cpp b/nci/jni/NfcTag.cpp
index 77b915f..6fa0281 100755
--- a/nci/jni/NfcTag.cpp
+++ b/nci/jni/NfcTag.cpp
@@ -49,6 +49,7 @@
*******************************************************************************/
NfcTag::NfcTag()
: mNumTechList(0),
+ mNumRfDiscId(0),
mTechnologyTimeoutsTable(MAX_NUM_TECHNOLOGY),
mNativeData(NULL),
mIsActivated(false),
@@ -103,6 +104,7 @@
mIsActivated = false;
mActivationState = Idle;
mProtocol = NFC_PROTOCOL_UNKNOWN;
+ mNumRfDiscId = 0;
mtT1tMaxMessageSize = 0;
mReadCompletedStatus = NFA_STATUS_OK;
resetTechnologies();
@@ -457,7 +459,7 @@
}
}
LOG(DEBUG) << StringPrintf("%s; mNumDiscTechList=%x", fn, mNumDiscTechList);
-
+ mNumRfDiscId = discovery_ntf.rf_disc_id;
TheEnd:
LOG(DEBUG) << StringPrintf("%s: exit", fn);
}
diff --git a/nci/jni/NfcTag.h b/nci/jni/NfcTag.h
index 698db4c..310df2b 100644
--- a/nci/jni/NfcTag.h
+++ b/nci/jni/NfcTag.h
@@ -46,6 +46,7 @@
// service received from
// RF_INTF_ACTIVATED NTF
int mNumTechList; // current number of NFC technologies in the list
+ int mNumRfDiscId;
/*******************************************************************************
**
diff --git a/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java b/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java
index 67099d4..2182640 100755
--- a/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java
+++ b/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java
@@ -16,21 +16,28 @@
package com.android.nfc.dhimpl;
+import static com.android.nfc.NfcStatsLog.NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__MODE_UNKNOWN;
+import static com.android.nfc.NfcStatsLog.NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__SUPPORT_WITHOUT_RF_DEACTIVATION;
+import static com.android.nfc.NfcStatsLog.NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__SUPPORT_WITH_RF_DEACTIVATION;
+
import android.content.Context;
-import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.PollingFrame;
import android.nfc.tech.Ndef;
import android.nfc.tech.TagTechnology;
import android.os.Bundle;
+import android.os.Trace;
import android.util.Log;
import com.android.nfc.DeviceHost;
import com.android.nfc.NfcDiscoveryParameters;
import com.android.nfc.NfcService;
+import com.android.nfc.NfcStatsLog;
import com.android.nfc.NfcVendorNciResponse;
-
+import com.android.nfc.NfcProprietaryCaps;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
@@ -42,10 +49,6 @@
static final String DRIVER_NAME = "android-nci";
- static {
- System.loadLibrary("nfc_nci_jni");
- }
-
/* Native structure */
private long mNative;
@@ -55,7 +58,7 @@
private final Object mLock = new Object();
private final HashMap<Integer, byte[]> mT3tIdentifiers = new HashMap<Integer, byte[]>();
-
+ private NfcProprietaryCaps mProprietaryCaps = null;
private static final int MIN_POLLING_FRAME_TLV_SIZE = 5;
private static final int TAG_FIELD_CHANGE = 0;
private static final int TAG_NFC_A = 1;
@@ -67,8 +70,13 @@
private static final int NCI_OID_INDEX = 1;
private static final int OP_CODE_INDEX = 3;
+ private void loadLibrary() {
+ System.loadLibrary("nfc_nci_jni");
+ }
+
public NativeNfcManager(Context context, DeviceHostListener listener) {
mListener = listener;
+ loadLibrary();
initializeNativeStructure();
mContext = context;
}
@@ -89,6 +97,11 @@
@Override
public boolean initialize() {
boolean ret = doInitialize();
+ if (mContext.getResources().getBoolean(
+ com.android.nfc.R.bool.nfc_proprietary_getcaps_supported)) {
+ mProprietaryCaps = NfcProprietaryCaps.createFromByteArray(getProprietaryCaps());
+ logProprietaryCaps(mProprietaryCaps);
+ }
mIsoDepMaxTransceiveLength = getIsoDepMaxTransceiveLength();
return ret;
}
@@ -162,13 +175,16 @@
}
return mContext.getResources().getBoolean(
- com.android.nfc.R.bool.nfc_observe_mode_supported);
+ com.android.nfc.R.bool.nfc_observe_mode_supported);
}
@Override
public native boolean setObserveMode(boolean enabled);
@Override
+ public native boolean isObserveModeEnabled();
+
+ @Override
public void registerT3tIdentifier(byte[] t3tIdentifier) {
synchronized (mLock) {
int handle = doRegisterT3tIdentifier(t3tIdentifier);
@@ -207,7 +223,7 @@
public native int getLfT3tMax();
@Override
- public native void doSetScreenState(int screen_state_mask);
+ public native void doSetScreenState(int screen_state_mask, boolean alwaysPoll);
@Override
public native int getNciVersion();
@@ -300,22 +316,6 @@
doDump(fd);
}
- private native void doEnableScreenOffSuspend();
-
- @Override
- public boolean enableScreenOffSuspend() {
- doEnableScreenOffSuspend();
- return true;
- }
-
- private native void doDisableScreenOffSuspend();
-
- @Override
- public boolean disableScreenOffSuspend() {
- doDisableScreenOffSuspend();
- return true;
- }
-
private native boolean doSetNfcSecure(boolean enable);
@Override
@@ -343,6 +343,7 @@
@Override
public native int getMaxRoutingTableSize();
+ public native boolean isMultiTag();
private native NfcVendorNciResponse nativeSendRawVendorCmd(
int mt, int gid, int oid, byte[] payload);
@@ -390,71 +391,84 @@
mListener.onHwErrorReported();
}
- private void notifyPollingLoopFrame(int data_len, byte[] p_data) {
+ public void notifyPollingLoopFrame(int data_len, byte[] p_data) {
if (data_len < MIN_POLLING_FRAME_TLV_SIZE) {
return;
}
- Bundle frame = new Bundle();
- final int header_len = 2;
+ Trace.beginSection("notifyPollingLoopFrame");
+ final int header_len = 4;
int pos = header_len;
- final int TLV_len_offset = 0;
- final int TLV_type_offset = 2;
+ final int TLV_header_len = 3;
+ final int TLV_type_offset = 0;
+ final int TLV_len_offset = 2;
final int TLV_timestamp_offset = 3;
final int TLV_gain_offset = 7;
final int TLV_data_offset = 8;
+ ArrayList<PollingFrame> frames = new ArrayList<PollingFrame>();
while (pos + TLV_len_offset < data_len) {
- int type = p_data[pos + TLV_type_offset];
- int length = p_data[pos + TLV_len_offset];
- if (pos + length + 1 > data_len) {
- // Frame is bigger than buffer.
- Log.e(TAG, "Polling frame data is longer than buffer data length.");
- break;
- }
- switch (type) {
- case TAG_FIELD_CHANGE:
- frame.putChar(
- HostApduService.POLLING_LOOP_TYPE_KEY,
- p_data[pos + TLV_data_offset] != 0x00
- ? HostApduService.POLLING_LOOP_TYPE_ON
- : HostApduService.POLLING_LOOP_TYPE_OFF);
+ @PollingFrame.PollingFrameType int frameType;
+ Bundle frame = new Bundle();
+ int type = p_data[pos + TLV_type_offset];
+ int length = p_data[pos + TLV_len_offset];
+ if (TLV_len_offset + length < TLV_gain_offset ) {
+ Log.e(TAG, "Length (" + length + ") is less than a polling frame, dropping.");
break;
- case TAG_NFC_A:
- frame.putChar(HostApduService.POLLING_LOOP_TYPE_KEY,
- HostApduService.POLLING_LOOP_TYPE_A);
+ }
+ if (pos + TLV_header_len + length > data_len) {
+ // Frame is bigger than buffer.
+ Log.e(TAG, "Polling frame data ("+ pos + ", " + length
+ + ") is longer than buffer data length (" + data_len + ").");
break;
- case TAG_NFC_B:
- frame.putChar(HostApduService.POLLING_LOOP_TYPE_KEY,
- HostApduService.POLLING_LOOP_TYPE_B);
- break;
- case TAG_NFC_F:
- frame.putChar(HostApduService.POLLING_LOOP_TYPE_KEY,
- HostApduService.POLLING_LOOP_TYPE_F);
- break;
- case TAG_NFC_UNKNOWN:
- frame.putChar(
- HostApduService.POLLING_LOOP_TYPE_KEY,
- HostApduService.POLLING_LOOP_TYPE_UNKNOWN);
- frame.putByteArray(
- HostApduService.POLLING_LOOP_DATA_KEY,
- Arrays.copyOfRange(
- p_data, pos + TLV_data_offset, pos + TLV_timestamp_offset + length));
- break;
- default:
- Log.e(TAG, "Unknown polling loop tag type.");
+ }
+ switch (type) {
+ case TAG_FIELD_CHANGE:
+ frameType = p_data[pos + TLV_data_offset] != 0x00
+ ? PollingFrame.POLLING_LOOP_TYPE_ON
+ : PollingFrame.POLLING_LOOP_TYPE_OFF;
+ break;
+ case TAG_NFC_A:
+ frameType = PollingFrame.POLLING_LOOP_TYPE_A;
+ break;
+ case TAG_NFC_B:
+ frameType = PollingFrame.POLLING_LOOP_TYPE_B;
+ break;
+ case TAG_NFC_F:
+ frameType = PollingFrame.POLLING_LOOP_TYPE_F;
+ break;
+ case TAG_NFC_UNKNOWN:
+ frameType = PollingFrame.POLLING_LOOP_TYPE_UNKNOWN;
+ break;
+ default:
+ Log.e(TAG, "Unknown polling loop tag type.");
+ return;
+ }
+ byte[] frameData = null;
+ if (pos + TLV_header_len + length <= data_len) {
+ frameData = Arrays.copyOfRange(p_data, pos + TLV_data_offset,
+ pos + TLV_header_len + length);
+ }
+ int gain = -1;
+ if (pos + TLV_gain_offset <= data_len) {
+ gain = Byte.toUnsignedInt(p_data[pos + TLV_gain_offset]);
+ if (gain == 0XFF) {
+ gain = -1;
+ }
+ }
+ long timestamp = 0;
+ if (pos + TLV_timestamp_offset + 3 < data_len) {
+ timestamp = Integer.toUnsignedLong(ByteBuffer.wrap(p_data,
+ pos + TLV_timestamp_offset, 4).order(ByteOrder.BIG_ENDIAN).getInt());
+ }
+ pos += (TLV_header_len + length);
+ frames.add(new PollingFrame(frameType, frameData, gain, timestamp, false));
}
- if (pos + TLV_gain_offset <= data_len) {
- byte gain = p_data[pos + TLV_gain_offset];
- frame.putByte(HostApduService.POLLING_LOOP_GAIN_KEY, gain);
- }
- if (pos + TLV_timestamp_offset + 3 < data_len) {
- int timestamp = ByteBuffer.wrap(p_data, pos + TLV_timestamp_offset, 4).order(ByteOrder.LITTLE_ENDIAN).getInt();
- frame.putInt(HostApduService.POLLING_LOOP_TIMESTAMP_KEY, timestamp);
- }
- pos += (length + 2);
- }
- mListener.onPollingLoopDetected(frame);
+ mListener.onPollingLoopDetected(frames);
+ Trace.endSection();
}
+ private void notifyWlcStopped(int wpt_end_condition) {
+ mListener.onWlcStopped(wpt_end_condition);
+ }
private void notifyVendorSpecificEvent(int event, int dataLen, byte[] pData) {
if (pData.length < NCI_HEADER_MIN_LEN || dataLen != pData.length) {
Log.e(TAG, "Invalid data");
@@ -481,7 +495,42 @@
@Override
public native void setTechnologyABRoute(int route);
+ private native byte[] getProprietaryCaps();
+
+ @Override
+ public native void enableVendorNciNotifications(boolean enabled);
+
private void notifyCommandTimeout() {
NfcService.getInstance().storeNativeCrashLogs();
}
+
+ /** wrappers for values */
+ private final int CAPS_OBSERVE_MODE_UNKNOWN =
+ NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__MODE_UNKNOWN;
+ private final int CAPS_OBSERVE_MODE_SUPPORT_WITH_RF_DEACTIVATION =
+ NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__SUPPORT_WITH_RF_DEACTIVATION;
+ private final int CAPS_OBSERVE_MODE_SUPPORT_WITHOUT_RF_DEACTIVATION =
+ NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__SUPPORT_WITHOUT_RF_DEACTIVATION;
+ private final int CAPS_OBSERVE_MODE_NOT_SUPPORTED =
+ NfcStatsLog.NFC_PROPRIETARY_CAPABILITIES_REPORTED__PASSIVE_OBSERVE_MODE__NOT_SUPPORTED;
+
+ private void logProprietaryCaps(NfcProprietaryCaps proprietaryCaps) {
+ int observeModeStatsd = CAPS_OBSERVE_MODE_UNKNOWN;
+
+ NfcProprietaryCaps.PassiveObserveMode mode = proprietaryCaps.getPassiveObserveMode();
+
+ if (mode == NfcProprietaryCaps.PassiveObserveMode.SUPPORT_WITH_RF_DEACTIVATION) {
+ observeModeStatsd = CAPS_OBSERVE_MODE_SUPPORT_WITH_RF_DEACTIVATION;
+ } else if (mode == NfcProprietaryCaps.PassiveObserveMode.SUPPORT_WITHOUT_RF_DEACTIVATION) {
+ observeModeStatsd = CAPS_OBSERVE_MODE_SUPPORT_WITHOUT_RF_DEACTIVATION;
+ } else if (mode == NfcProprietaryCaps.PassiveObserveMode.NOT_SUPPORTED) {
+ observeModeStatsd = CAPS_OBSERVE_MODE_NOT_SUPPORTED;
+ }
+
+ NfcStatsLog.write(NfcStatsLog.NFC_PROPRIETARY_CAPABILITIES_REPORTED,
+ observeModeStatsd,
+ proprietaryCaps.isPollingFrameNotificationSupported(),
+ proprietaryCaps.isPowerSavingModeSupported(),
+ proprietaryCaps.isAutotransactPollingLoopFilterSupported());
+ }
}
diff --git a/nci/src/com/android/nfc/dhimpl/NativeNfcTag.java b/nci/src/com/android/nfc/dhimpl/NativeNfcTag.java
index e50acbc..30a826d 100755
--- a/nci/src/com/android/nfc/dhimpl/NativeNfcTag.java
+++ b/nci/src/com/android/nfc/dhimpl/NativeNfcTag.java
@@ -922,4 +922,32 @@
}
}
}
+
+ @Override
+ public NdefMessage getNdef() {
+ Log.d(TAG, "getNdef: Searching for NfcCharging information");
+ int[] ndefinfo = new int[2];
+ int status;
+ NdefMessage ndefMsg = null;
+ status = checkNdefWithStatus(ndefinfo);
+ if (status != 0) {
+ Log.d(TAG, "Check NDEF Failed - status = " + status);
+ return ndefMsg;
+ }
+
+ byte[] buff = readNdef();
+ if (buff != null && buff.length > 0) {
+ try {
+ ndefMsg = new NdefMessage(buff);
+ } catch (FormatException e) {
+ // Create an intent anyway, without NDEF messages
+ ndefMsg = null;
+ }
+ } else if (buff != null) {
+ // Empty buffer, unformatted tags fall into this case
+ ndefMsg = null;
+ }
+
+ return ndefMsg;
+ }
}
diff --git a/nci/src/com/android/nfc/dhimpl/NativeP2pDevice.java b/nci/src/com/android/nfc/dhimpl/NativeP2pDevice.java
deleted file mode 100755
index 1b1359b..0000000
--- a/nci/src/com/android/nfc/dhimpl/NativeP2pDevice.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2010 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 com.android.nfc.dhimpl;
-
-import com.android.nfc.DeviceHost.NfcDepEndpoint;
-
-/**
- * Native interface to the P2P Initiator functions
- */
-public class NativeP2pDevice implements NfcDepEndpoint {
-
- private int mHandle;
-
- private int mMode;
-
- private byte[] mGeneralBytes;
-
- private native byte[] doReceive();
- @Override
- public byte[] receive() {
- return doReceive();
- }
-
- private native boolean doSend(byte[] data);
- @Override
- public boolean send(byte[] data) {
- return doSend(data);
- }
-
- private native boolean doConnect();
- @Override
- public boolean connect() {
- return doConnect();
- }
-
- private native boolean doDisconnect();
- @Override
- public boolean disconnect() {
- return doDisconnect();
- }
-
- public native byte[] doTransceive(byte[] data);
- @Override
- public byte[] transceive(byte[] data) {
- return doTransceive(data);
- }
-
- @Override
- public int getHandle() {
- return mHandle;
- }
-
- @Override
- public int getMode() {
- return mMode;
- }
-
- @Override
- public byte[] getGeneralBytes() {
- return mGeneralBytes;
- }
-}
diff --git a/proguard.flags b/proguard.flags
index 226fd0a..652fb18 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1,3 +1,6 @@
# Keep all impl classes referenced via JNI.
-keep class com.android.nfc.dhimpl**.Native* { *; }
+# Keep NfcCharging class since it's usage is behind a trunk stable flag (which is disabled in `-next` config).
+-keep class com.android.nfc.wlc.NfcCharging { *; }
-keep class com.android.nfc.NfcVendorNciResponse { *; }
+-keep class com.android.nfc.proto.** { *; }
diff --git a/proto/Android.bp b/proto/Android.bp
new file mode 100644
index 0000000..6bb73db
--- /dev/null
+++ b/proto/Android.bp
@@ -0,0 +1,18 @@
+package {
+ default_team: "trendy_team_fwk_nfc",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "nfc-event-log-proto",
+ proto: {
+ type: "lite",
+ },
+ sdk_version: "system_current",
+ min_sdk_version: "35",
+ srcs: ["event.proto"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
+}
diff --git a/proto/event.proto b/proto/event.proto
new file mode 100644
index 0000000..3cd0ab9
--- /dev/null
+++ b/proto/event.proto
@@ -0,0 +1,65 @@
+//
+// 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.
+//
+
+syntax = "proto2";
+
+package com_android_nfc;
+
+option java_package = "com.android.nfc.proto";
+option java_outer_classname = "NfcEventProto";
+
+// Proto used to format NFC event logs.
+message EventList {
+ repeated Event events = 1;
+}
+
+message Event {
+ required string timestamp = 1;
+ required EventType event_type = 2;
+}
+
+// Union of events.
+message EventType {
+ oneof _EventType {
+ NfcBootupState bootup_state = 1;
+ NfcStateChange state_change = 2;
+ NfcReaderModeChange reader_mode_change = 3;
+ NfcCeUnroutableAid ce_unroutable_aid = 4;
+ }
+}
+
+message NfcBootupState {
+ required bool enabled = 1;
+}
+
+message NfcAppInfo {
+ optional string package_name = 1;
+ optional int32 uid = 2;
+}
+
+message NfcStateChange {
+ required NfcAppInfo app_info = 1;
+ required bool enabled = 2;
+}
+
+message NfcReaderModeChange {
+ optional NfcAppInfo app_info = 1;
+ required int32 flags = 2;
+}
+
+message NfcCeUnroutableAid {
+ required string aid = 1;
+}
diff --git a/res/layout/url_open_confirmation_tablet.xml b/res/layout/url_open_confirmation_tablet.xml
new file mode 100644
index 0000000..b3994ec
--- /dev/null
+++ b/res/layout/url_open_confirmation_tablet.xml
@@ -0,0 +1,35 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:gravity="start"
+ android:layout_width="wrap_content"
+ android:layout_height="400dp"
+ android:orientation="vertical"
+ android:paddingTop="24dp"
+ android:paddingStart="24dp"
+ android:paddingEnd="24dp">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:paddingBottom="24dp"
+ android:text="@string/summary_confirm_url_open_tablet"
+ android:textColor="?android:attr/textColorPrimaryInverse"/>
+ <TextView
+ android:id="@+id/url_open_confirmation_link"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/colorAccent"/>
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="24dp"
+ android:paddingBottom="24dp">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@drawable/ic_weblink_nfc"/>
+ </FrameLayout>
+
+</LinearLayout>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index c149b7b..b4b4d03 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Straal"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Die program het nie toestemming vir eksterne berging nie. Dit word vereis om hierdie lêer te straal"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Maak skakel oop?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Jou tablet het \'n skakel deur NFC ontvang:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Jou foon het \'n skakel deur NFC ontvang:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Jou foon het \'n skakel deur NFC ontvang:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Jou tablet het ’n skakel deur NFC ontvang:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Maak skakel oop"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-leesfout. Probeer weer."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Geen gesteunde program vir hierdie NFC-merker nie"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tik om uit te vind hoe om dit reg te stel."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-data word tans opgeneem"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tik om opname te stop."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Laat <xliff:g id="PKG">%1$s</xliff:g> toe om NFC te aktiveer?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nee"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 18b427b..171fbaf 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"መተግበሪያ የውጫዊ ማከማቻ ፈቃድ የለውም። ይሄ ይህን ፋይል በሞገድ ለመውሰድ ያስፈልጋል"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"አገናኝ ይከፈት?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"የእርስዎ ጡባዊ በNFC በኩል አገናኝ ተቀብሏል፦"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"የእርስዎ ስልክ በNFC በኩል አገናኝ ተቀብሏል፦"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"የእርስዎ ስልክ በNFC በኩል አገናኝ ተቀብሏል፦"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"የእርስዎ ጡባዊ በኤንኤፍሲ በኩል አገናኝ ተቀብሏል፦"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"አገናኝ ክፈት"</string>
<string name="tag_read_error" msgid="2485274498885877547">"የNFC ማንበብ ስህተት። እንደገና ይሞክሩ።"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ለዚህ የNFC መለያ የሚደገፍ መተግበሪያ የለም"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"እንዴት ማስተካከል እንደሚቻል ለማወቅ መታ ያድርጉ።"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"የኤንኤፍሲ ውሂብ እየተቀዳ ነው"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"መቅዳት ለማቆም መታ ያድርጉ።"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> ኤንኤፍሲን እንዲያነቃ ይፈቀድ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"አዎ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"አይ"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 3af3fcd..f6cc465 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"شعاع Android"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"لا يحتوي التطبيق على إذن مساحة تخزين خارجية، إلا أنه مطلوب لإرسال هذا الملف"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"هل تريد فتح الرابط؟"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"تلقى جهازك اللوحي رابطًا عبر ميزة NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"تلقى هاتفك رابطًا عبر ميزة NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"تلقى هاتفك رابطًا عبر ميزة NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"تلقّى جهازك اللوحي رابطًا من خلال تقنية NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"فتح الرابط"</string>
<string name="tag_read_error" msgid="2485274498885877547">"حدث خطأ NFC. يُرجى إعادة المحاولة."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ليس هناك تطبيق متوافق مع علامة NFC هذه."</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"انقر لمعرفة كيف يمكنك حلّ هذه المشكلة."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"يتم حاليًا تسجيل بيانات الاتصال القصير المدى (NFC)"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"انقر لإيقاف التسجيل."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"هل تريد السماح لحِزمة \"<xliff:g id="PKG">%1$s</xliff:g>\" بتفعيل NFC؟"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"نعم"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"لا"</string>
</resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index ef6c32f..2721aa3 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android বীম"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"এপ্লিকেশ্বনটোৰ বাহ্যিক ষ্ট’ৰেজ ব্যৱহাৰ কৰাৰ অনুমতি নাই৷ এই ফাইলটো বীম কৰিবলৈ এয়া প্ৰয়োজন।"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"লিংক খুলিবনে?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"আপোনাৰ টেবলেটে NFC যোগে এই লিংক পাইছে:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"আপোনাৰ ফ\'নে NFC যোগে এই লিংক পাইছে:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"আপোনাৰ ফ\'নে NFC যোগে এই লিংক পাইছে:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"আপোনাৰ টেবলেটে NFCৰ জৰিয়তে এই লিংক পাইছে:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"লিংক খোলক"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC পঢ়াত আসোঁৱাহ। আকৌ চেষ্টা কৰক।"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"এই NFC টেগৰ বাবে কোনো সমৰ্থিত এপ্লিকেশ্বন নাই"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"কেনেকৈ সমাধান কৰিব লাগে সেয়া জানিবলৈ টিপক।"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ডেটা ৰেকৰ্ড কৰি থকা হৈছে"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ৰেকৰ্ড কৰা বন্ধ কৰিবলৈ টিপক।"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g>ক NFC সক্ষম কৰিবলৈ দিবনে?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"হয়"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"নহয়"</string>
</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 162e6c8..890f651 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Tətbiqdə Xarici Yaddaş İcazəsi yoxdur. Faylı Köçürmək tələb olunur"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Link açılsın?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Planşet NFC ilə link əldə etdi:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefon NFC ilə link əldə etdi:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefon NFC ilə link əldə etdi:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Planşetə NFC ilə keçid göndərilib:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Linki açın"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC oxuma xətası. Yenidən cəhd edin."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"NFC Teq üçün dəstəklənən tətbiq yoxdur"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Necə düzəldəcəyinizi öyrənmək üçün toxunun."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC datası qeydə alınır"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Qeydə almanı dayandırmaq üçün toxunun."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> üçün NFC-ni aktivləşdirmək icazəsi verilsin?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Bəli"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Xeyr"</string>
</resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b5b25cf..830ba36 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android prebacivanje"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacija nema dozvolu za spoljnu memoriju. To je potrebno za prebacivanje ove datoteke"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Želite li da otvorite link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablet je primio link preko NFC-a:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefon je primio link preko NFC-a:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefon je primio link preko NFC-a:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablet je primio link preko NFC-a:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otvori link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Došlo je do greške u čitanju NFC oznake. Probajte ponovo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nijedna aplikacija ne podržava ovu NFC oznaku"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Dodirnite da biste saznali kako da rešite problem."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC podaci se snimaju"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Dodirnite da biste zaustavili snimanje."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Želite da dozvolite da <xliff:g id="PKG">%1$s</xliff:g> omogući NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Da"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index df7ff99..c222dbf 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"У праграмы няма дазволу на доступ да знешняга сховішча. Гэты дазвол патрабуецца, каб перадаць дадзены файл"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Адкрыць спасылку?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"На планшэт праз NFC прыйшла спасылка:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"На тэлефон праз NFC прыйшла спасылка:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"На тэлефон праз NFC прыйшла спасылка:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"На планшэт па NFC прыйшла спасылка:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Адкрыць спасылку"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Памылка чытання NFC. Паўтарыце спробу."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Няма праграм, якія падтрымліваюцца для гэтага цэтліка NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Націсніце, каб даведацца, як вырашыць праблему."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Даныя NFC запісваюцца"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Націсніце, каб спыніць запіс."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Даць пакету \"<xliff:g id="PKG">%1$s</xliff:g>\" дазвол уключыць NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Так"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Не"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index aa4cb42..a05e65c 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Лъч"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Приложението няма разрешение за външно хранилище. То се изисква за излъчването на този файл"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Да се отвори ли връзката?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Таблетът ви получи връзка през NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Телефонът ви получи връзка през NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Телефонът ви получи връзка през NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Таблетът ви получи връзка през NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Отваряне на връзката"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Грешка при четене за NFC. Опитайте отново."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Няма приложение, което поддържа този маркер за NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Докоснете, за да научите как да отстраните проблема."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Записват се данни посредством NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Докоснете за спиране на записването."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Да се разреши ли на <xliff:g id="PKG">%1$s</xliff:g> да активира NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Да"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Не"</string>
</resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index d7a0ba7..2715531 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android বীম"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ডিভাইসের বাইরের মেমরি ব্যবহার করার অনুমতি এই অ্যাপ্লিকেশানটিকে দেওয়া হয়নি। এই ফাইলটি বিম করার জন্য অনুমতিটি প্রয়োজন"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"লিঙ্কটি খুলবেন?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"আপনার ট্যাবলেট NFC এর মাধ্যমে একটি লিঙ্ক পেয়েছে:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"আপনার ফোন NFC এর মাধ্যমে একটি লিঙ্ক পেয়েছে:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"আপনার ফোন NFC এর মাধ্যমে একটি লিঙ্ক পেয়েছে:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"আপনার ট্যাবলেট NFC-এর মাধ্যমে একটি লিঙ্ক পেয়েছে:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"লিঙ্ক খুলুন"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC পড়তে সমস্যা হয়েছে। আবার চেষ্টা করুন।"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"এই NFC ট্যাগ ব্যবহার করার মতো কোনও অ্যাপ্লিকেশন নেই"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"কীভাবে সমস্যার সমাধান করবেন তা জানতে ট্যাপ করুন।"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ডেটা রেকর্ড করা হচ্ছে"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"রেকর্ডিং বন্ধ করতে ট্যাপ করুন।"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC চালু করার জন্য <xliff:g id="PKG">%1$s</xliff:g>-কে অনুমতি দেবেন?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"হ্যাঁ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"না"</string>
</resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 42e1418..6b2c690 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacija nema odobrenje za vanjsku pohranu. Ovo je potrebno kako biste ovaj fajl mogli prenijeti koristeći funkciju Beam."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Otvoriti link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Vaš tablet je primio link preko NFC-a:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Vaš telefon je primio link preko NFC-a:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Vaš telefon je primio link preko NFC-a:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablet je primio link putem NFC-a:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otvori link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Greška prilikom čitanja NFC-a. Pokušajte ponovo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nema podržane aplikacije za ovu NFC oznaku"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Dodirnite da saznate kako ispraviti problem."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC podaci se trenutno bilježe"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Dodirnite da zaustavite bilježenje."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Dozvoliti da <xliff:g id="PKG">%1$s</xliff:g> omogući NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Da"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2992f52..a91703f 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"L\'aplicació no té permís d\'emmagatzematge extern. Aquest permís és necessari per compartir el fitxer."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vols obrir l\'enllaç?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Has rebut un enllaç a la tauleta per NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Has rebut un enllaç al telèfon per NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Has rebut un enllaç al telèfon per NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Has rebut un enllaç a la tauleta per NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Obre l\'enllaç"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Hi ha hagut un error en llegir l\'etiqueta NFC. Torna-ho a provar."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No hi ha cap aplicació compatible amb aquesta etiqueta NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Toca per obtenir informació sobre com pots resoldre-ho."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"S\'estan gravant les dades de l\'NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Toca per aturar la gravació."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vols permetre que <xliff:g id="PKG">%1$s</xliff:g> activi la funció NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sí"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 4ff0739..c525c33 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Teleport Android"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikace nemá přístup k externímu úložišti. Přístup je vyžadován k odeslání souboru pomocí funkce Beam."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Otevřít odkaz?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablet obdržel odkaz přes NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefon obdržel odkaz přes NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefon obdržel odkaz přes NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablet obdržel odkaz přes NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otevřít odkaz"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Při čtení NFC došlo k chybě. Zkuste to znovu."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Pro tuto značku NFC není k dispozici žádná podporovaná aplikace"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Klepnutím zjistíte, jak problém vyřešit."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Data funkce NFC jsou zaznamenávána"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Klepnutím ukončíte nahrávání."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Povolit aplikaci <xliff:g id="PKG">%1$s</xliff:g> zapnout NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ano"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index dfec0eb..15907cb 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Appen har ikke adgang til det eksterne lager. Adgangstilladelse er påkrævet for at overføre denne fil."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vil du åbne linket?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Du har modtaget et link på din tablet via NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Du har modtaget et link på din telefon via NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Du har modtaget et link på din telefon via NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Du har modtaget et link på din tablet via NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Åbn link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-læsefejl. Prøv igen."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Der er ingen understøttede apps til dette NFC-tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tryk for at se, hvordan du løser problemet."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-data registreres"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tryk for at stoppe optagelsen."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vil du give <xliff:g id="PKG">%1$s</xliff:g> tilladelse til at aktivere NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nej"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index da1efb7..d8a95d1 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Die App ist nicht berechtigt, auf externe Speicher zuzugreifen. Dies ist jedoch zum Beamen der Datei erforderlich."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Link öffnen?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Du hast auf dem Tablet via NFC einen Link erhalten:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Du hast auf dem Smartphone via NFC einen Link erhalten:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Du hast auf dem Smartphone via NFC einen Link erhalten:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Du hast auf dem Tablet via NFC einen Link erhalten:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Link öffnen"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-Lesefehler. Wiederholen."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Keine unterstützte Anwendung für dieses NFC-Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tippen, um das Problem zu beheben"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-Daten werden aufgezeichnet"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tippen, um die Aufzeichnung zu beenden"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Zulassen, dass <xliff:g id="PKG">%1$s</xliff:g> NFC aktiviert?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nein"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index bd5c54b..20b85dd 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Η εφαρμογή δεν διαθέτει Άδεια εξωτερικού αποθηκευτικού χώρου. Αυτή η άδεια απαιτείται για την ζεύξη αυτού του αρχείου"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Να ανοίξει ο σύνδεσμος;"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Το tablet έλαβε έναν σύνδεσμο μέσω NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Το τηλέφωνό σας έλαβε έναν σύνδεσμο μέσω NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Το τηλέφωνό σας έλαβε έναν σύνδεσμο μέσω NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Το tablet έλαβε έναν σύνδεσμο μέσω NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Άνοιγμα συνδέσμου"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Σφάλμα ανάγνωσης NFC. Δοκιμάστε ξανά."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Δεν υπάρχει υποστηριζόμενη εφαρμογή για αυτή την ετικέτα NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Πατήστε για να μάθετε πώς μπορείτε να διορθώσετε το πρόβλημα."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Τα δεδομένα NFC εγγράφονται"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Πατήστε για διακοπή της εγγραφής."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Να επιτρέπεται στο <xliff:g id="PKG">%1$s</xliff:g> να ενεργοποιεί το NFC;"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ναι"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Όχι"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 16ef46e..dac0cbd 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Application does not have External Storage Permission. This is required to Beam this file"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Open link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Your tablet received a link through NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Your tablet received a link through NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Open link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC read error. Try again."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No supported application for this NFC Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tap to learn how to fix."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC data is being recorded"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tap to stop recording."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Allow <xliff:g id="PKG">%1$s</xliff:g> to enable NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Yes"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 16ef46e..f34dc73 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -46,8 +46,9 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Application does not have External Storage Permission. This is required to Beam this file"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Open link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Your tablet received a link through NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <!-- no translation found for summary_confirm_url_open_tablet (771152442325809851) -->
+ <skip />
<string name="action_confirm_url_open" msgid="3458322738812921189">"Open link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC read error. Try again."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No supported application for this NFC Tag"</string>
@@ -55,4 +56,10 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tap to learn how to fix."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC data is being recorded"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tap to stop recording."</string>
+ <!-- no translation found for title_package_enabling_nfc (5736481508428918024) -->
+ <skip />
+ <!-- no translation found for enable_nfc_yes (694867197186062792) -->
+ <skip />
+ <!-- no translation found for enable_nfc_no (6549033065900624599) -->
+ <skip />
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 16ef46e..dac0cbd 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Application does not have External Storage Permission. This is required to Beam this file"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Open link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Your tablet received a link through NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Your tablet received a link through NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Open link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC read error. Try again."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No supported application for this NFC Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tap to learn how to fix."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC data is being recorded"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tap to stop recording."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Allow <xliff:g id="PKG">%1$s</xliff:g> to enable NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Yes"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 16ef46e..dac0cbd 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Application does not have External Storage Permission. This is required to Beam this file"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Open link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Your tablet received a link through NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Your tablet received a link through NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Open link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC read error. Try again."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No supported application for this NFC Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tap to learn how to fix."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC data is being recorded"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tap to stop recording."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Allow <xliff:g id="PKG">%1$s</xliff:g> to enable NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Yes"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index e6b80dc..e345240 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Application does not have External Storage Permission. This is required to Beam this file"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Open link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Your tablet received a link through NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Your phone received a link through NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Your tablet received a link through NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Open link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC read error. Try again."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No supported application for this NFC Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tap to learn how to fix."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC data is being recorded"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tap to stop recording."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Allow <xliff:g id="PKG">%1$s</xliff:g> to enable NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Yes"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e912e7c..9f90229 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"La aplicación no tiene permiso de almacenamiento externo, lo que es necesario para transmitir este archivo"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"¿Abrir vínculo?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tu tablet recibió un vínculo mediante NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Tu teléfono recibió un vínculo mediante NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Tu teléfono recibió un vínculo mediante NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"La tablet recibió un vínculo a través de NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Abrir vínculo"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Error de lectura de NFC. Vuelve a intentarlo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No hay apps compatibles con esta etiqueta NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Presiona para ver cómo corregir el problema."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Los datos de NFC se están registrando"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Presiona para dejar de registrarlos."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"¿Quieres permitir que <xliff:g id="PKG">%1$s</xliff:g> habilite la NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sí"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index fa5f9fd..3e61a2f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"La aplicación no tiene permiso de almacenamiento externo, necesario para compartir este archivo"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"¿Quieres abrir el enlace?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Has recibido un enlace en tu tablet por NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Has recibido un enlace en tu teléfono por NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Has recibido un enlace en tu teléfono por NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Has recibido un enlace en tu tablet por NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Abrir enlace"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Error de lectura de NFC. Inténtalo de nuevo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"No hay ninguna aplicación compatible con esta etiqueta NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Toca para saber cómo solucionarlo."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Los datos de NFC se están registrando"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Toca para detener el registro."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"¿Permitir que <xliff:g id="PKG">%1$s</xliff:g> habilite la opción NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sí"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index a57709f..657ba0a 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Rakendusel pole luba välise salvestusruumi kasutamiseks. See on vajalik faili kiirega edastamiseks"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Kas avada link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Teie tahvelarvuti sai lingi NFC kaudu:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Teie telefon sai lingi NFC kaudu:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Teie telefon sai lingi NFC kaudu:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Teie tahvelarvuti sai lingi NFC kaudu:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Ava link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-kiibi lugemise viga. Proovige uuesti."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Selle NFC-kiibi puhul toetatud rakendus puudub"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Puudutage, et hankida parandamise kohta lisateavet."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC andmeid salvestatakse"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Puudutage salvestamise alustamiseks."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Kas lubada paketil <xliff:g id="PKG">%1$s</xliff:g> võimaldada NFC kasutamine?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Jah"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ei"</string>
</resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 753d742..d66f296 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -46,13 +46,16 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikazioak ez dauka kanpoko memoria atzitzeko baimenik. Fitxategia NFC bidez partekatzeko da beharrezkoa baimen hori."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Esteka ireki nahi duzu?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Esteka bat jaso duzu tabletan NFC bidez:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Esteka bat jaso duzu telefonoan NFC bidez:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Esteka bat jaso duzu telefonoan NFC bidez:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Esteka bat jaso duzu tabletan NFC bidez:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Ireki esteka"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Errore bat gertatu da NFC etiketa irakurtzean. Saiatu berriro."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ez dago NFC etiketa hau onartzen duen aplikaziorik"</string>
<string name="nfc_blocking_alert_title" msgid="1086172436984457085">"Baliteke NFC bidezko konexioa blokeatzea"</string>
- <string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Sakatu hau konponbideei buruzko informazioa lortzeko."</string>
+ <string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Sakatu hau arazoa konpontzeko argibideak lortzeko."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFCari buruzko datuak erregistratzen"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Sakatu hau grabatzeari uzteko."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC gaitzeko baimena eman nahi diozu <xliff:g id="PKG">%1$s</xliff:g> zerbitzuari?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Bai"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ez"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 187ad3d..24eb963 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"برنامه،ِ مجوز فضای ذخیرهسازی خارجی را ندارد. این مجوز برای «پرتوی» فایل لازم است"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"پیوند باز شود؟"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"رایانه لوحیتان پیوندی ازطریق NFC دریافت کرد:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"تلفنتان پیوندی ازطریق NFC دریافت کرد:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"تلفنتان پیوندی ازطریق NFC دریافت کرد:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"رایانه لوحیتان پیوندی ازطریق NFC دریافت کرد:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"باز کردن پیوند"</string>
<string name="tag_read_error" msgid="2485274498885877547">"خطای خواندن «ارتباطات میداننزدیک» (NFC). دوباره امتحان کنید."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"برنامه پشتیبانیشدهای برای نشان NFC وجود ندارد"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"برای آشنایی با نحوه برطرف کردن مشکل، ضربه بزنید."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"دادههای NFC ضبط میشود"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"برای توقف ضبط، ضربه بزنید."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"به <xliff:g id="PKG">%1$s</xliff:g> اجازه میدهید NFC را فعال کند؟"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"بله"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"نه"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index fe32f2f..5f110d0 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Sovelluksella ei ole ulkoisen tallennustilan käyttöoikeutta. Se tarvitaan, jotta tämän sovelluksen sisältöä voidaan jakaa."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Avataanko linkki?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablettisi vastaanotti linkin NFC:n kautta:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Puhelimesi vastaanotti linkin NFC:n kautta:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Puhelimesi vastaanotti linkin NFC:n kautta:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablettisi vastaanotti linkin NFC:n kautta:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Avaa linkki"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-lukuvirhe. Yritä uudelleen."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ei NFC-tagia tukevia sovelluksia"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Napauta, jos haluat oppia korjaamaan ongelman."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-dataa tallennetaan"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Lopeta tallennus napauttamalla."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Saako <xliff:g id="PKG">%1$s</xliff:g> ottaa NFC:n käyttöön?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Kyllä"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ei"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 62191b0..860520b 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"L\'application ne possède pas l\'autorisation d\'accès aux dispositifs de stockage externes. Cela est nécessaire pour partager ce fichier."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Ouvrir le lien?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Votre tablette a reçu un lien par CCP :"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Votre téléphone a reçu un lien par CCP :"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Votre téléphone a reçu un lien par CCP :"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Votre tablette a reçu un lien par CCP :"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Ouvrir le lien"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Erreur de lecture CCP. Réessayez."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Aucune application prise en charge pour cette balise CCP"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Touchez pour apprendre comment la débloquer."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Les données CCP sont enregistrées"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Touchez pour cesser l\'enregistrement."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Autoriser <xliff:g id="PKG">%1$s</xliff:g> à activer la CCP?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Oui"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Non"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 71a06b2..38bf2d7 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"L\'application n\'est pas autorisée à accéder à l\'espace de stockage externe. Or, cette autorisation est requise pour partager ce fichier."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Ouvrir le lien ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Votre tablette a reçu un lien via NFC :"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Votre téléphone a reçu un lien via NFC :"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Votre téléphone a reçu un lien via NFC :"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Votre tablette a reçu un lien via NFC :"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Ouvrir le lien"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Erreur de lecture NFC. Veuillez réessayer."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Aucune application compatible avec ce tag NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Appuyez pour savoir comment y remédier."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Enregistrement de données NFC…"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Appuyez pour arrêter l\'enregistrement."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Autoriser <xliff:g id="PKG">%1$s</xliff:g> à activer le NFC ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Oui"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Non"</string>
</resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index becfae4..32baef4 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"A aplicación non ten permiso de acceso ao almacenamento externo, pero é necesario para transferir este ficheiro"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Queres abrir a ligazón?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"A túa tableta recibiu unha ligazón mediante NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"O teu teléfono recibiu unha ligazón mediante NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"O teu teléfono recibiu unha ligazón mediante NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"A túa tableta recibiu unha ligazón mediante NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Abrir ligazón"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Produciuse un erro de lectura da etiqueta NFC. Téntao de novo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Non hai ningunha aplicación compatible para esta etiqueta NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Toca para ver como podes solucionalo."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Estanse gravando os datos de NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Toca para deter a gravación."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Queres permitir que <xliff:g id="PKG">%1$s</xliff:g> active a NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Si"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Non"</string>
</resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index c63d029..ad869f2 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android બીમ"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"આ ઍપ્લિકેશન પાસે બાહ્ય સ્ટોરેજની પરવાનગી નથી. આ ફાઇલને બીમ કરવા માટે એ જરૂરી છે"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"લિંક ખોલીએ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"તમારા ટૅબ્લેટને NFC મારફતે એક લિંક પ્રાપ્ત થઈ છે:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"તમારા ફોનને NFC મારફતે એક લિંક પ્રાપ્ત થઈ છે:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"તમારા ફોનને NFC મારફતે એક લિંક પ્રાપ્ત થઈ છે:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"તમારા ટૅબ્લેટને NFC મારફતે એક લિંક પ્રાપ્ત થઈ છે:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"લિંક ખોલો"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC વાંચનમાં ભૂલ. ફરી પ્રયાસ કરો."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"આ NFC ટૅગ માટે કોઈ સહાયક ઍપ્લિકેશન નથી"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"સુધારવાની રીતે વિશે જાણવા માટે ટૅપ કરો."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ડેટા રેકોર્ડ કરવામાં આવી રહ્યો છે"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"રેકોર્ડિંગ રોકવા માટે ટૅપ કરો."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"શું <xliff:g id="PKG">%1$s</xliff:g>ને NFC ચાલુ કરવાની મંજૂરી આપીએ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"હા"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ના"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 5682f13..94e6726 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android बीम"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ऐप्लिकेशन में बाहरी जगह की अनुमति नहीं है. इस फ़ाइल को बीम करने के लिए यह अनुमति ज़रूरी है"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"लिंक को खोलें?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"आपके टैबलेट पर NFC के ज़रिए एक लिंक आया है:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"आपके फ़ोन पर NFC के ज़रिए एक लिंक आया है:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"आपके फ़ोन पर NFC के ज़रिए एक लिंक आया है:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"आपके टैबलेट पर एनएफ़सी के ज़रिए एक लिंक आया है:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"लिंक को खोलें"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC की जानकारी नहीं पता की जा सकी. कृपया फिर से कोशिश करें."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ऐसा कोई ऐप्लिकेशन मौजूद नहीं है, जिससे यह एनएफ़सी टैग इस्तेमाल किया जा सके"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"इस समस्या को ठीक करने का तरीका जानने के लिए टैप करें."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"एनएफ़सी का डेटा रिकाॅर्ड किया जा रहा है"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"रिकॉर्डिंग बंद करने के लिए टैप करें."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"क्या एनएफ़सी को चालू करने के लिए <xliff:g id="PKG">%1$s</xliff:g> को अनुमति देनी है?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"हां"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"नहीं"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 02389c6..e8d8225 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacija nema dopuštenje za vanjsku pohranu. To je potrebno za emitiranje datoteke"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Želite li otvoriti vezu?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Vaš je tablet primio vezu NFC-om:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Vaš je telefon primio vezu NFC-om:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Vaš je telefon primio vezu NFC-om:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Vaš je tablet primio vezu NFC-om:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otvori vezu"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Pogreška pri čitanju NFC-a. Pokušajte ponovo."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nema podržane aplikacije za ovu oznaku NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Dodirnite da biste saznali kako to ispraviti."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Snimaju se podaci NFC-a"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Dodirnite da biste zaustavili snimanje."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Želite li paketu <xliff:g id="PKG">%1$s</xliff:g> dopustiti da omogući NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Da"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 7c0b28e..f3ba677 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Az alkalmazás nem rendelkezik a fájl sugárzásához szükséges Külső tárolási engedéllyel."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Megnyitja a linket?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Táblagépe linket kapott NFC-n keresztül:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefonja linket kapott NFC-n keresztül:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefonja linket kapott NFC-n keresztül:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Táblagépe linket kapott NFC-n keresztül:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Link megnyitása"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-olvasási hiba. Próbálja újra."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nem található támogatott alkalmazás ehhez az NFC-címkéhez"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Koppintással további információkhoz juthat a probléma elhárításáról."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"A rendszer rögzíti az NFC-adatokat"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Koppintson a rögzítés leállításához."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Engedélyezi a(z) <xliff:g id="PKG">%1$s</xliff:g> számára az NFC bekapcsolását?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Igen"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nem"</string>
</resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index a06697c..daad903 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Հավելվածը չունի արտաքին հիշողություն օգտագործելու թույլտվություն։ Այն անհրաժեշտ է ֆայլը փոխանցելու համար"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Բացե՞լ հղումը"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Ձեր պլանշետին ուղարկվել է հղում NFC-ի միջոցով:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Ձեր հեռախոսին ուղարկվել է հղում NFC-ի միջոցով:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Ձեր հեռախոսին ուղարկվել է հղում NFC-ի միջոցով:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Ձեր պլանշետին ուղարկվել է հղում NFC-ի միջոցով"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Բացել հղումը"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Չհաջողվեց կարդալ NFC պիտակը։ Նորից փորձեք:"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Այս NFC պիտակի համար աջակցվող հավելված չկա"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Հպեք և իմացեք՝ ինչպես այն շտկել։"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-ի տվյալները գրանցվում են"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Հպեք՝ տվյալների գրանցումը կանգնեցնելու համար։"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Թույլատրե՞լ <xliff:g id="PKG">%1$s</xliff:g>-ին միացնել NFC-ն"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Այո"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ոչ"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index e9bdc63..a91533b 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikasi tidak memiliki Izin Penyimpanan Eksternal. Diperlukan untuk melakukan beam file ini"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Buka link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablet Anda menerima link melalui NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Ponsel Anda menerima link melalui NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Ponsel Anda menerima link melalui NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablet Anda menerima link melalui NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Buka link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Pembacaan NFC error. Coba lagi."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Aplikasi tidak didukung untuk Tag NFC ini"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Ketuk untuk mempelajari cara memperbaikinya."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Data NFC sedang direkam"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Ketuk untuk menghentikan perekaman."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Izinkan <xliff:g id="PKG">%1$s</xliff:g> untuk mengaktifkan NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ya"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Tidak"</string>
</resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 76d00a7..0096620 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Forritið er ekki með heimild til ytri geymslu. Hana þarf til að hægt sé að senda þessa skrá"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Opna tengil?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Spjaldtölvan þín fékk tengil um NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Síminn þinn fékk tengil um NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Síminn þinn fékk tengil um NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Spjaldtölvan þín fékk tengil í gegnum NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Opna tengil"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Villa í lestri NFC-merkis. Reyndu aftur."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ekkert stutt forrit fyrir þetta NFC-merki"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Ýttu til að kynna þér hvernig má lagfæra þetta."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-gögn eru skráð"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Ýttu til að hætta upptöku."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Leyfa <xliff:g id="PKG">%1$s</xliff:g> að kveikja á NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Já"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nei"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 3a47efe..915c5ab 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"L\'applicazione non presenta l\'autorizzazione per lo spazio di archiviazione esterno che è obbligatoria per trasmettere questo file."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Aprire il link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Hai ricevuto un link tramite NFC sul tablet:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Hai ricevuto un link tramite NFC sul telefono:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Hai ricevuto un link tramite NFC sul telefono:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Hai ricevuto un link tramite NFC sul tablet:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Apri il link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Errore durante la lettura del tag NFC. Riprova."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nessuna app supportata per questo tag NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tocca per scoprire come risolvere il problema."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"È in corso la registrazione dei dati NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tocca per interrompere la registrazione."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Consentire a <xliff:g id="PKG">%1$s</xliff:g> di attivare NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sì"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"No"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 6bfd088..21cd0c1 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"לאפליקציה אין הרשאת גישה לאחסון החיצוני. ההרשאה הזו נדרשת כדי להעביר את הקובץ"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"לפתוח את הקישור?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"הטאבלט קיבל קישור דרך NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"הטלפון קיבל קישור דרך NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"הטלפון קיבל קישור דרך NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"התקבל קישור בטאבלט דרך NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"לפתיחת הקישור"</string>
<string name="tag_read_error" msgid="2485274498885877547">"אירעה שגיאת קריאה של NFC. יש לנסות שוב."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"אין אפליקציה תומכת לתג NFC זה"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"יש להקיש כדי לראות איך לפתור את הבעיה."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"נתוני ה-NFC מתועדים"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"יש להקיש כדי להפסיק את ההקלטה."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"להרשות ל-<xliff:g id="PKG">%1$s</xliff:g> להפעיל NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"כן"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"לא"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 5faeca5..953fe19 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Androidビーム"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"アプリに外部ストレージへのアクセス権限が付与されていません。このファイルをビームするには、権限を付与してください"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"リンクを開きますか?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"タブレットで NFC を使用してリンクを受信しました。"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"スマートフォンで NFC を使用してリンクを受信しました。"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"スマートフォンで NFC を使用してリンクを受信しました。"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"タブレットで NFC を使用してリンクを受信しました。"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"リンクを開く"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC 読み取りエラーが発生しました。もう一度お試しください。"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"この NFC タグをサポートしているアプリはありません"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"タップして修正方法をご確認ください。"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC データを記録しています"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"タップすると記録を停止します。"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> が NFC を有効にすることを許可しますか?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"はい"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"いいえ"</string>
</resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index f38b171..a49f6ff 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android სხივი"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"აპლიკაციისთვის გარე მეხსიერების ნებართვა მინიჭებული არაა. ამ ფაილის სხივით გადასაცემად, ეს აუცილებელია"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"გსურთ ბმულის გახსნა?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"თქვენმა ტაბლეტმა მიიღო ბმული NFC-ს მეშვეობით:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"თქვენმა ტელეფონმა მიიღო ბმული NFC-ს მეშვეობით:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"თქვენმა ტელეფონმა მიიღო ბმული NFC-ს მეშვეობით:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"თქვენმა ტაბლეტმა მიიღო ბმული NFC-ის მეშვეობით:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ბმულის გახსნა"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-ს წაკითხვის შეცდომა. ცადეთ ისევ."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ამ NFC ტეგისთვის არცერთი მხარდაჭერილი აპლიკაცია არ მოიძებნა"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"შეეხეთ იმის შესატყობად, თუ როგორ უნდა მოაგვაროთ ეს."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"მიმდინარეობს NFC მონაცემების ჩაწერა"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"შეეხეთ ჩაწერის შესაწყვეტად."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"მიეცეს <xliff:g id="PKG">%1$s</xliff:g> პაკეტს NFC-ს ჩართვის უფლება?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"დიახ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"არა"</string>
</resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 35a65d4..2f0bd57 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Қолданбаның сыртқы жад рұқсаты жоқ. Ол осы файлды таратуға қажет"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Сілтемені ашу керек пе?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Планшетіңіз NFC арқылы сілтеме алды:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Телефоныңыз NFC арқылы сілтеме алды:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Телефоныңыз NFC арқылы сілтеме алды:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Планшетіңіз NFC арқылы сілтеме алды:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Сілтемені ашу"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC оқу қатесі шықты. Қайталап көріңіз."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Осы NFC картасы үшін бірде-бір қолдау көрсетілетін қолданба жоқ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Қалай түзетуге болатынын білу үшін түртіңіз."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC деректері жазылып жатыр"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Жазуды тоқтату үшін түртіңіз."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> қызметіне NFC технологиясын қосуға рұқсат етілсін бе?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Иә"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Жоқ"</string>
</resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index a4dcdef..601618b 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"កម្មវិធីមិនមានការអនុញ្ញាតសម្រាប់ទំហំផ្ទុកខាងក្រៅទេ។ វាត្រូវការការអនុញ្ញាតដើម្បីបញ្ជូនឯកសារនេះ។"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"បើកតំណ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ថេប្លេតរបស់អ្នកទទួលបានតំណមួយតាមរយៈ NFC៖"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ទូរសព្ទរបស់អ្នកទទួលបានតំណមួយតាមរយៈ NFC៖"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ទូរសព្ទរបស់អ្នកទទួលបានតំណមួយតាមរយៈ NFC៖"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ថេប្លេតរបស់អ្នកទទួលបានតំណមួយតាមរយៈ NFC៖"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"បើកតំណ"</string>
<string name="tag_read_error" msgid="2485274498885877547">"មានបញ្ហាក្នុងការអាន NFC។ សូមព្យាយាមម្ដងទៀត។"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"មិនមានកម្មវិធីដែលអាចប្រើស្លាក NFC នេះទេ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ចុច ដើម្បីស្វែងយល់អំពីរបៀបដោះស្រាយ។"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"ទិន្នន័យ NFC កំពុងត្រូវបានថត"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ចុចដើម្បីបញ្ឈប់ការថត។"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"អនុញ្ញាតឱ្យ <xliff:g id="PKG">%1$s</xliff:g> បើក NFC ឬ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"បាទ/ចាស"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ទេ"</string>
</resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index a8b4ddd..39d5887 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ಅಪ್ಲಿಕೇಶನ್ ಬಾಹ್ಯ ಸಂಗ್ರಹಣಾ ಅನುಮತಿಯನ್ನು ಹೊಂದಿಲ್ಲ. ಈ ಫೈಲ್ ಅನ್ನು ಬೀಮ್ ಮಾಡಲು ಇದರ ಅಗತ್ಯವಿದೆ"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"ಲಿಂಕ್ ತೆರೆಯುವುದೇ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ NFC ಮೂಲಕ ಲಿಂಕ್ ಸ್ವೀಕರಿಸಿದೆ:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ನಿಮ್ಮ ಫೋನ್ NFC ಮೂಲಕ ಲಿಂಕ್ ಸ್ವೀಕರಿಸಿದೆ:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ನಿಮ್ಮ ಫೋನ್ NFC ಮೂಲಕ ಲಿಂಕ್ ಸ್ವೀಕರಿಸಿದೆ:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್ NFC ಮೂಲಕ ಲಿಂಕ್ ಸ್ವೀಕರಿಸಿದೆ:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ಲಿಂಕ್ ತೆರೆಯಿರಿ"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC ಓದುವಾಗ ದೋಷ ಕಂಡು ಬಂದಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ಈ NFC ಟ್ಯಾಗ್ಗೆ ಯಾವುದೇ ಆ್ಯಪ್ ಬೆಂಬಲಿಸುವುದಿಲ್ಲ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ಹೇಗೆ ಸರಿಪಡಿಸುವುದು ಎಂಬುದನ್ನು ತಿಳಿಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ಡೇಟಾವನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ರೆಕಾರ್ಡಿಂಗ್ ನಿಲ್ಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು <xliff:g id="PKG">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸಬೇಕೆ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ಹೌದು"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ಇಲ್ಲ"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 8e777c0..3ea1570 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"애플리케이션에 외부 저장소 권한이 없습니다. 이 파일을 공유하려면 외부 저장소 권한이 필요합니다."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"링크를 여시겠습니까?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"태블릿에서 NFC를 통해 링크 수신:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"휴대전화에서 NFC를 통해 링크 수신:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"휴대전화에서 NFC를 통해 링크 수신:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"태블릿에서 NFC를 통해 링크를 수신함:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"링크 열기"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC 읽기 오류입니다. 다시 시도하세요."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"이 NFC 태그에 지원되는 애플리케이션이 없습니다."</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"탭하여 해결 방법을 확인하세요."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC 데이터를 기록 중입니다"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"기록을 중지하려면 탭하세요."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g>에서 NFC를 사용하도록 허용하시겠습니까?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"예"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"아니요"</string>
</resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index e0cdab3..dfbbe5f 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Колдонмонун тышкы сактагычты пайдаланууга уруксаты жок. Бул файлды өткөрүү үчүн мындай уруксат берилиши керек"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Шилтеме ачылсынбы?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Планшетиңизге NFC аркылуу шилтеме келди:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Телефонуңузга NFC аркылуу шилтеме келди:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Телефонуңузга NFC аркылуу шилтеме келди:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Планшетиңизге NFC аркылуу шилтеме келди:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Шилтемени ачуу"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC\'ни окууда ката кетти. Кайталап көрүңүз."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Бул NFC энбелгиси үчүн колдоого алынган колдонмо жок"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Кантип оңдоо керектигин билүү үчүн, бул жерди басып коюңуз."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC маалыматы жаздырылууда"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Жаздырууну токтотуу үчүн таптап коюңуз."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> кызматына NFC\'ни иштетүүгө уруксат бересизби?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ооба"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Жок"</string>
</resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 0d03490..d97af90 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ແອັບພລິເຄຊັນບໍ່ມີສິດອະນຸຍາດໃຊ້ບ່ອນຈັດເກັບຂໍ້ມູນພາຍນອກ. ສິດອະນຸຍາດນີ້ຈຳເປັນສຳລັບການສົ່ງໄຟລ໌ນີ້"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"ເປີດລິ້ງ"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ແທັບເລັດຂອງທ່ານໄດ້ຮັບລິ້ງຜ່ານທາງ NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ໂທລະສັບຂອງທ່ານໄດ້ຮັບລິ້ງຜ່ານທາງ NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ໂທລະສັບຂອງທ່ານໄດ້ຮັບລິ້ງຜ່ານທາງ NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ແທັບເລັດຂອງທ່ານໄດ້ຮັບລິ້ງຜ່ານທາງ NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ເປີດລິ້ງ"</string>
<string name="tag_read_error" msgid="2485274498885877547">"ການອ່ານ NFC ຜິດພາດ. ກະລຸນາລອງໃໝ່."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ບໍ່ມີແອັບພລິເຄຊັນທີ່ຮອງຮັບແທັກ NFC ນີ້"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ແຕະເພື່ອສຶກສາວິທີແກ້ໄຂ."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"ກຳລັງບັນທຶກຂໍ້ມູນ NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ແຕະເພື່ອຢຸດການບັນທຶກ."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"ອະນຸຍາດໃຫ້ <xliff:g id="PKG">%1$s</xliff:g> ເປີດການນຳໃຊ້ NFC ບໍ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ແມ່ນ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ບໍ່"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 9e3b876..ad1337b 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"„Android“ perdavimas"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Programai nesuteiktas išorinės saugyklos leidimas. Tai būtina, kad būtų galima perduoti šį failą"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Atidaryti nuorodą?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Planšetiniame kompiuteryje gauta nuoroda naudojant ALR:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefone gauta nuoroda naudojant ALR:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefone gauta nuoroda naudojant ALR:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Planšetiniame kompiuteryje gauta nuoroda naudojant ALR:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Atidaryti nuorodą"</string>
<string name="tag_read_error" msgid="2485274498885877547">"ALR nuskaitymo klaida. Bandykite dar kartą."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nėra šią ALR žymą palaikančių programų"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Palieskite, kad sužinotumėte, kaip sutvarkyti."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC duomenys įrašomi"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Palieskite, kad sustabdytumėte įrašymą."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Leisti <xliff:g id="PKG">%1$s</xliff:g> norint įgalinti NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Taip"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 0048799..2752375 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Lietojumprogrammai nav ārējās krātuves atļaujas. Tā ir nepieciešama, lai varētu kopīgot šo failu, izmantojot funkciju Beam."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vai atvērt saiti?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Jūsu planšetdatorā tika saņemta saite, izmantojot NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Jūsu tālrunī tika saņemta saite, izmantojot NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Jūsu tālrunī tika saņemta saite, izmantojot NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Jūsu planšetdatorā tika saņemta saite, izmantojot NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Atvērt saiti"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC lasīšanas kļūda. Mēģiniet vēlreiz."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Šī NFC atzīme netiek atbalstīta nevienā lietojumprogrammā"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Pieskarieties, lai uzzinātu, kā to novērst."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Notiek NFC datu reģistrēšana"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Lai apturētu reģistrēšanu, pieskarieties."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vai atļaut pakotnei <xliff:g id="PKG">%1$s</xliff:g> iespējot NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Jā"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nē"</string>
</resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 4a566b8..7ef5454 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Апликацијата нема дозвола за надворешната меморија. Тоа е неопходно за пренесување на датотекава"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Да се отвори линкот?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Вашиот таблет доби линк преку NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Вашиот телефон доби линк преку NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Вашиот телефон доби линк преку NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Вашиот таблет доби линк преку NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Отвори ја врската"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Грешка при читањето со NFC. Обидете се повторно."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Нема поддржана апликација за оваа ознака за NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Допрете за да дознаете како да го решите проблемот."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Се снимаат податоци од NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Допрете за да престане снимањето."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Да се дозволи <xliff:g id="PKG">%1$s</xliff:g> да овозможи NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Да"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Не"</string>
</resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 06a5fa4..3c6e411 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android ബീം"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ആപ്പിന് എക്സ്റ്റേണൽ സ്റ്റോറേജ് അനുമതി ഇല്ല. ഈ ഫയൽ ബീംചെയ്യാൻ ഇത് ആവശ്യമാണ്"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"ലിങ്ക് തുറക്കണോ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"നിങ്ങളുടെ ടാബ്ലെറ്റിന് NFC വഴി ഒരു ലിങ്ക് ലഭിച്ചു:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"നിങ്ങളുടെ ഫോണിന് NFC വഴി ഒരു ലിങ്ക് ലഭിച്ചു:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"നിങ്ങളുടെ ഫോണിന് NFC വഴി ഒരു ലിങ്ക് ലഭിച്ചു:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"നിങ്ങളുടെ ടാബ്ലെറ്റിന് NFC വഴി ഒരു ലിങ്ക് ലഭിച്ചു:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ലിങ്ക് തുറക്കുക"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC റീഡ് ചെയ്യുന്നതിൽ പിശക്. വീണ്ടും ശ്രമിക്കുക."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ഈ NFC ടാഗിനെ പിന്തുണയ്ക്കുന്ന ആപ്പുകളൊന്നുമില്ല"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"പരിഹരിക്കുന്നത് എങ്ങനെയെന്ന് മനസ്സിലാക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ഡാറ്റ റെക്കോർഡ് ചെയ്യുന്നുണ്ട്"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"റെക്കോർഡ് ചെയ്യുന്നത് നിർത്താൻ ടാപ്പ് ചെയ്യുക."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC പ്രവർത്തനക്ഷമമാക്കാൻ <xliff:g id="PKG">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കണോ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ഉവ്വ്"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ഇല്ല"</string>
</resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 546b299..0ef6a20 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Андройд Цацраг"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Аппад Гадаад сангийн зөвшөөрөл алга. Энэ файлыг дамжуулахад энэ зөвшөөрөл шаардлагатай"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Холбоосыг нээх үү?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Таны таблет NFC-р холбоос авсан байна:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Таны утас NFC-р холбоос авсан байна:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Таны утас NFC-р холбоос авсан байна:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Таны таблет NFC-р дамжуулан холбоос хүлээн авсан:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Холбоосыг нээх"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-г уншихад алдаа гарлаа. Дахин оролдоно уу."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Энэ NFC Шошгыг зохицуулах боломжтой дэмжигдсэн апп алга"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Хэрхэн засах зааврыг харахын тулд товшино уу."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC өгөгдлийг бичиж байна"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Бичлэгийг зогсоохын тулд товшино уу."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g>-д NFC-г идэвхжүүлэхийг зөвшөөрөх үү?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Тийм"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Үгүй"</string>
</resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index a97276f..53acbef 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android बीम"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ॲप्लिकेशनला बाह्य स्टोरेज परवानगी नाही. ही फाइल बीम करण्यासाठी हे आवश्यक आहे"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"लिंक उघडायची का?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"तुमच्या टॅबलेटला NFC वरून एक लिंक मिळाली आहे:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"तुमच्या फोनला NFC वरून एक लिंक मिळाली आहे:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"तुमच्या फोनला NFC वरून एक लिंक मिळाली आहे:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"तुमच्या टॅबलेटवर NFC द्वारे लिंक मिळाली आहे:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"लिंक उघडा"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC वाचण्यात एरर आली. पुन्हा प्रयत्न करा."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"या NFC टॅग साठी कोणतेही सपोर्ट करणारे ॲप्लिकेशन नाही"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"निराकरण कसे करायचे ते जाणून घेण्यासाठी टॅप करा."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC डेटा रेकॉर्ड केला जात आहे"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"रेकॉर्डिंग थांबवण्यासाठी टॅप करा."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> ला NFC सुरू करण्याची अनुमती द्यायची आहे का?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"होय"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"नाही"</string>
</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index a289ef1..0adedc5 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Pancaran Android"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikasi tiada Kebenaran Storan Luaran. Kebenaran ini diperlukan untuk Memancarkan fail ini"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Buka pautan?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablet anda menerima pautan melalui NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefon anda menerima pautan melalui NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefon anda menerima pautan melalui NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablet anda menerima pautan melalui NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Buka pautan"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Ralat baca NFC. Cuba lagi."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Tiada aplikasi yang disokong untuk Teg NFC ini"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Ketik untuk mengetahui cara membetulkan perkara ini."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Data NFC sedang dirakamkan"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Ketik untuk menghentikan rakaman."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Benarkan <xliff:g id="PKG">%1$s</xliff:g> untuk mendayakan NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ya"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Tidak"</string>
</resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 1e89e3b..005a343 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"အပလီကေးရှင်းတွင် ပြင်ပတွင်သိုလှောင်ခွင့် မပါဝင်ပါ။ ဤဖိုင်ကို လှိုင်းလွှင့်ရန် ၎င်းလိုအပ်ပါသည်"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"လင့်ခ်ကို ဖွင့်လိုပါသလား။"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"သင့်တက်ဘလက်သည် NFC မှတစ်ဆင့် လင့်ခ်တစ်ခု လက်ခံရရှိထားပါသည်-"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"သင့်ဖုန်းသည် NFC မှတစ်ဆင့် လင့်ခ်တစ်ခု လက်ခံရရှိထားပါသည်-"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"သင့်ဖုန်းသည် NFC မှတစ်ဆင့် လင့်ခ်တစ်ခု လက်ခံရရှိထားပါသည်-"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"သင့်တက်ဘလက်သည် NFC မှတစ်ဆင့် လင့်ခ်တစ်ခု လက်ခံရရှိထားပါသည်-"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"လင့်ခ်ဖွင့်ရန်"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC ဖတ်ရှုမှု အမှားရှိနေသည်။ ထပ်စမ်းကြည့်ပါ။"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ဤ \'NFC တဂ်\' အတွက် ပံ့ပိုးထားသော အပလီကေးရှင်း မရှိပါ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ပြုပြင်နည်းကို လေ့လာရန် တို့ပါ။"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ဒေတာကို မှတ်တမ်းတင်နေသည်"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"မှတ်တမ်းတင်ခြင်းရပ်ရန် တို့ပါ။"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC ဖွင့်ရန် <xliff:g id="PKG">%1$s</xliff:g> ကို ခွင့်ပြုမလား။"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ဖွင့်မည်"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"မဖွင့်ပါ"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 280f2f8..d805843 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Appen har ikke tillatelse for ekstern lagring. Dette er påkrevd for å beame denne filen."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vil du åpne linken?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Nettbrettet ditt mottok en link via NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefonen din mottok en link via NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefonen din mottok en link via NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Nettbrettet mottok en link via NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Åpne link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-lesefeil. Prøv på nytt."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ingen apper støttes for denne NFC-brikken"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Trykk for å finne ut hvordan du løser dette."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Det registreres NFC-data"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Trykk for å stoppe registreringen."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vil du tillate at <xliff:g id="PKG">%1$s</xliff:g> aktiverer NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nei"</string>
</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 6e0ce9d..b894c81 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -20,7 +20,7 @@
<string name="connected_peripheral" msgid="20748648543160091">"जडित <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
<string name="connect_peripheral_failed" msgid="7925702596242839275">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> जडान गर्न सकेन"</string>
<string name="disconnecting_peripheral" msgid="1443699384809097200">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> विच्छेदन गर्दै"</string>
- <string name="disconnected_peripheral" msgid="4470578100296504366">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> विच्छेद गरियो"</string>
+ <string name="disconnected_peripheral" msgid="4470578100296504366">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> डिस्कनेक्ट गरियो"</string>
<string name="pairing_peripheral" msgid="6983626861540899365">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> जोडा गर्दै"</string>
<string name="pairing_peripheral_failed" msgid="6087643307743264679">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> जोडा गर्न सकेन"</string>
<string name="failed_to_enable_bt" msgid="7229153323594758077">"ब्लुटुथ सक्षम पार्न सकेन"</string>
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android बिम"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"एपलाई बाह्य भण्डारणसम्बन्धी अनुमति छैन। यो फाइल बिम गर्न अनुमति अनिवार्य छ"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"लिंक खोल्ने हो?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"तपाईंको ट्याब्लेटमा NFC मार्फत एउटा लिंक आयो:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"तपाईंको फोनमा NFC मार्फत एउटा लिंक आयो:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"तपाईंको फोनमा NFC मार्फत एउटा लिंक आयो:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"तपाईंको ट्याब्लेटमा NFC मार्फत एउटा लिंक आयो:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"लिंक खोल्नुहोस्"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC रिड गर्दा त्रुटि भयो। फेरि प्रयास गर्नुहोस्।"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"यो NFC ट्यागको कुनै पनि समर्थित एप छैन"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"यो ब्लक हटाउने तरिका जान्न ट्याप गर्नुहोस्।"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC डेटा रेकर्ड गरिँदै छ"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"रेकर्ड गर्न छाड्न ट्याप गर्नुहोस्।"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> लाई NFC अन गर्न दिने हो?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"अँ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"अहँ"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 4aa99fe..ec039b1 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"De app heeft geen rechten voor externe opslag. Dit recht is nodig om het bestand te beamen."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Link openen?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Je tablet heeft een link ontvangen via NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Je telefoon heeft een link ontvangen via NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Je telefoon heeft een link ontvangen via NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Je tablet heeft een link gekregen via NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Link openen"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Fout bij lezen NFC. Probeer het opnieuw."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Geen ondersteunde app voor deze NFC-tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tik voor meer informatie over hoe je dit kunt verhelpen."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-gegevens worden opgenomen"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tik om te stoppen met opnemen."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Toestaan dat <xliff:g id="PKG">%1$s</xliff:g> NFC aanzet?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nee"</string>
</resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index c843244..a771908 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android ବିମ୍"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ଆପ୍ଲୀକେଶନ୍କୁ ଏକ୍ସଟର୍ନଲ୍ ଷ୍ଟୋରେଜ୍ରେ ଲେଖିବାର ଅନୁମତି ନାହିଁ। ଏହି ଫାଇଲ୍କୁ ବିମ୍ କରିବା ଲାଗି ଏହାର ଅନୁମତି ଆବଶ୍ୟକ"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"ଲିଙ୍କ ଖୋଲିବେ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ଆପଣଙ୍କ ଟାବଲେଟ୍କୁ NFC ମାଧ୍ୟମରେ ଏକ ଲିଙ୍କ ଆସିଛି:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ଆପଣଙ୍କ ଫୋନ୍କୁ NFC ମାଧ୍ୟମରେ ଏକ ଲିଙ୍କ ଆସିଛି:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ଆପଣଙ୍କ ଫୋନ୍କୁ NFC ମାଧ୍ୟମରେ ଏକ ଲିଙ୍କ ଆସିଛି:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ଆପଣଙ୍କ ଟାବଲେଟ୍କୁ NFC ମାଧ୍ୟମରେ ଏକ ଲିଙ୍କ ଆସିଛି:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ଲିଙ୍କ ଖୋଲନ୍ତୁ"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC ରିଡ୍ କରିବା ବେଳେ ତ୍ରୁଟି। ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ଏହି NFC ଟ୍ୟାଗ୍ ପାଇଁ କୌଣସି ସାମର୍ଥିତ ଆପ୍ଲିକେସନ୍ ନାହିଁ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"କିପରି ସମାଧାନ କରିବେ ସେ ବିଷୟରେ ଜାଣିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ଡାଟା ରେକର୍ଡ କରାଯାଉଛି"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ରେକର୍ଡିଂ ବନ୍ଦ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFCକୁ ସକ୍ଷମ କରିବା ପାଇଁ <xliff:g id="PKG">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ହଁ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ନା"</string>
</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 71ca6dc..2f52654 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android ਬੀਮ"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ਐਪਲੀਕੇਸ਼ਨ ਕੋਲ ਬਾਹਰੀ ਸਟੋਰੇਜ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ। ਫ਼ਾਈਲ ਬੀਮ ਕਰਨ ਲਈ ਇਸਦੀ ਲੋੜ ਹੈ"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"ਕੀ ਲਿੰਕ ਖੋਲ੍ਹਣਾ ਹੈ?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ ਨੂੰ NFC ਰਾਹੀਂ ਇੱਕ ਲਿੰਕ ਪ੍ਰਾਪਤ ਹੋਇਆ:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੂੰ NFC ਰਾਹੀਂ ਇੱਕ ਲਿੰਕ ਪ੍ਰਾਪਤ ਹੋਇਆ:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ਤੁਹਾਡੇ ਫ਼ੋਨ ਨੂੰ NFC ਰਾਹੀਂ ਇੱਕ ਲਿੰਕ ਪ੍ਰਾਪਤ ਹੋਇਆ:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ਤੁਹਾਡੇ ਟੈਬਲੈੱਟ \'ਤੇ NFC ਰਾਹੀਂ ਲਿੰਕ ਆਇਆ ਹੈ:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"ਲਿੰਕ ਖੋਲ੍ਹੋ"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC ਪੜ੍ਹਨ ਵਿੱਚ ਗੜਬੜ ਹੋਈ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ਇਸ NFC ਟੈਗ ਲਈ ਕੋਈ ਸਮਰਥਿਤ ਐਪਲੀਕੇਸ਼ਨ ਨਹੀਂ ਹੈ"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ਇਸ ਨੂੰ ਠੀਕ ਕਰਨ ਦਾ ਤਰੀਕਾ ਜਾਣਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ਡਾਟੇ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ਰਿਕਾਰਡਿੰਗ ਬੰਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"ਕੀ <xliff:g id="PKG">%1$s</xliff:g> ਨੂੰ NFC ਚਾਲੂ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ਹਾਂ"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ਨਹੀਂ"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 0ce307d..1593ebc 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacja nie ma uprawnień do korzystania z pamięci zewnętrznej. Jest to wymagane, by można było przesłać ten plik zbliżeniowo"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Otworzyć link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Na tablet został przesłany link przez NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Na telefon został przesłany link przez NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Na telefon został przesłany link przez NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Na tablet został przesłany link przez NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otwórz link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Błąd odczytu tagu NFC. Spróbuj ponownie."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Brak obsługiwanej aplikacji dla tego tagu NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Kliknij, by dowiedzieć się, jak to naprawić."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Trwa rejestrowanie danych NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Kliknij, aby zatrzymać rejestrowanie."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Zezwolić pakietowi <xliff:g id="PKG">%1$s</xliff:g> na włączenie NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Tak"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nie"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index cd344ec..747bc7b 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"A app não tem autorização de armazenamento externo. Esta é necessária para transmitir este ficheiro."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Quer abrir o link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"O seu tablet recebeu um link através de NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"O seu telemóvel recebeu um link através de NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"O seu telemóvel recebeu um link através de NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"O seu tablet recebeu um link através de NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Abrir link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Erro de leitura de NFC. Tente novamente."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Não existe nenhuma app suportada para esta etiqueta NFC."</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Toque para saber como resolver."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Os dados de NFC estão a ser gravados"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Toque para parar a gravação."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Permitir que <xliff:g id="PKG">%1$s</xliff:g> ative o NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sim"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Não"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 19dd9dd..552aba4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"O aplicativo não tem permissão de armazenamento externo. Isso é necessário para enviar este arquivo"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Abrir link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Seu tablet recebeu um link por NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Seu smartphone recebeu um link por NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Seu smartphone recebeu um link por NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Seu tablet recebeu um link por NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Abrir link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Erro na leitura da NFC. Tente novamente."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nenhum aplicativo compatível com esta etiqueta NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Toque para aprender a resolver o problema."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Os dados NFC estão sendo gravados"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Toque para parar a gravação."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Permitir que <xliff:g id="PKG">%1$s</xliff:g> ative a NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Sim"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Não"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 14b2ea0..030356e 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplicația nu are permisiune de stocare externă. Aceasta este necesară pentru transmiterea fișierului."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Accesezi linkul?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tableta ta a primit un link prin NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefonul tău a primit un link prin NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefonul tău a primit un link prin NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tableta ta a primit un link prin NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Accesează linkul"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Eroare de citire NFC. Încearcă din nou."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nu există aplicații acceptate pentru această etichetă NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Atinge ca să afli cum să remediezi problema."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Se înregistrează date NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Atinge ca să oprești înregistrarea."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Permiți ca <xliff:g id="PKG">%1$s</xliff:g> să activeze NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Da"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nu"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index ef91295..a5c5609 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"У приложения нет разрешения на использование внешнего хранилища. Без этого передать файл невозможно."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Открыть ссылку?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"На ваш планшет по NFC поступила ссылка:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"На ваш телефон по NFC поступила ссылка:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"На ваш телефон по NFC поступила ссылка:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"На ваш планшет пришла по NFC ссылка:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Открыть"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Ошибка чтения NFC-метки. Повторите попытку."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Для этой NFC-метки нет поддерживаемых приложений"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Нажмите, чтобы узнать, как устранить эту проблему."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Выполняется запись данных NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Нажмите, чтобы остановить ее."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Разрешить пакету \"<xliff:g id="PKG">%1$s</xliff:g>\" включить NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Да"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Нет"</string>
</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index d810706..edfb0b5 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android බීම්"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"යෙදුමට බාහිර ගබඩා අවසරය නොමැත. මෙය මෙම ගොනුව කදම්බ කිරීමට අවශ්යයි"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"සබැදිය විවෘත කරන්නද?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"ඔබගේ ටැබ්ලට් පරිගණකයට NFC හරහා සබැඳියක් ලැබුණි:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"ඔබගේ දුරකථනයට NFC හරහා සබැඳියක් ලැබුණි:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"ඔබගේ දුරකථනයට NFC හරහා සබැඳියක් ලැබුණි:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"ඔබේ ටැබ්ලටයට NFC හරහා සබැඳියක් ලැබුණි:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"සබැඳිය විවෘත කරන්න"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC කියවීමේ දෝෂයකි. නැවත උත්සාහ කරන්න."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"මෙම NFC ටැගය සඳහා සහාය දක්වන යෙදුම් නැත"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"නිවැරදි කරන ආකාරය දැන ගැනීමට තට්ටු කරන්න."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC දත්ත වාර්තා කරමින් පවතී"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"පටිගත කිරීම නැවැත්වීමට තට්ටු කරන්න."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFC සබල කිරීමට <xliff:g id="PKG">%1$s</xliff:g> හට ඉඩ දෙන්න ද?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ඔව්"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"නැත"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 2b61c63..d18f20a 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikácia nemá povolenie používať externé úložisko. Toto povolenie sa vyžaduje, ak chcete daný súbor preniesť pomocou funkcie Beam."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Otvoriť odkaz?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablet dostal odkaz cez NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefón dostal odkaz cez NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefón dostal odkaz cez NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Do tabletu prišiel odkaz cez NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Otvoriť odkaz"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Pri čítaní štítku NFC sa vyskytla chyba. Skúste to znova."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Pre túto značku NFC nie je k dispozícii žiadna podporovaná aplikácia"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Klepnutím zistite, ako to opraviť."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Nahrávajú sa údaje NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Nahrávanie zastavíte klepnutím."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Chcete povoliť balíku <xliff:g id="PKG">%1$s</xliff:g> aktivovať NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Áno"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nie"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 5a1693d..60675a1 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacija nima dovoljenja za zunanjo shrambo. Dovoljenje je obvezno za prenos te datoteke."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Želite odpreti povezavo?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tablični računalnik je prejel povezavo prek vmesnika NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefon je prejel povezavo prek vmesnika NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefon je prejel povezavo prek vmesnika NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tablični računalnik je prejel povezavo prek vmesnika NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Odpri povezavo"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Napaka pri branju oznake NFC. Poskusite znova."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Za to oznako NFC ni podprta nobena aplikacija"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Dotaknite se, če želite izvedeti, kako odpravite to težavo."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Poteka snemanje podatkov NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Za ustavitev snemanja se dotaknite."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Ali dovolite, da paket <xliff:g id="PKG">%1$s</xliff:g> omogoči NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Da"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ne"</string>
</resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 07bc96c..2478198 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Dërgimi me rreze i androidit"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Aplikacioni nuk ka leje për hapësirën ruajtëse të jashtme. Kjo kërkohet për dërgimin e këtij skedari me rreze"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Të hapet lidhja?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tableti yt mori një lidhje nëpërmjet NFC-së:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefoni yt mori një lidhje nëpërmjet NFC-së:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefoni yt mori një lidhje nëpërmjet NFC-së:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tableti yt mori një lidhje nëpërmjet NFC-së:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Hap lidhjen"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Gabim në leximin e NFC-s. Provo përsëri."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Nuk ka asnjë aplikacion të mbështetur për këtë etiketë NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Trokit për të mësuar se si ta rregullosh."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Të dhënat e lidhjes NFC po regjistrohen"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Trokit për të ndaluar regjistrimin"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Të lejohet që <xliff:g id="PKG">%1$s</xliff:g> të aktivizojë NFC-në?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Po"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Jo"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 037c884..b29473c 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android пребацивање"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Апликација нема дозволу за спољну меморију. То је потребно за пребацивање ове датотеке"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Желите ли да отворите линк?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Таблет је примио линк преко NFC-а:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Телефон је примио линк преко NFC-а:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Телефон је примио линк преко NFC-а:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Таблет је примио линк преко NFC-а:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Отвори линк"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Дошло је до грешке у читању NFC ознаке. Пробајте поново."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ниједна апликација не подржава ову NFC ознаку"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Додирните да бисте сазнали како да решите проблем."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC подаци се снимају"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Додирните да бисте зауставили снимање."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Желите да дозволите да <xliff:g id="PKG">%1$s</xliff:g> омогући NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Да"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Не"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 661550c..8049670 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Appen har inte behörighet till externt lagringsutrymme. Detta krävs för att överföra filen trådlöst."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vill du öppna länken?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"En länk har skickats till surfplattan via NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"En länk har skickats till mobilen via NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"En länk har skickats till mobilen via NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"En länk har skickats till surfplattan via NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Öppna länk"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-läsfel. Försök igen."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Ingen app har stöd för den här NFC-etiketten"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Tryck här för att ta reda på hur du åtgärdar detta."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC-data spelas in"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Tryck för att sluta inspelningen."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vill du tillåta att <xliff:g id="PKG">%1$s</xliff:g> aktiverar NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ja"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Nej"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 0a573cf..3718ae7 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Programu haina Ruhusa ya kutumia Hifadhi ya Nje. Hii inahitajika ili Kusambaza faili hii."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Ungependa kufungua kiungo?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Kompyuta kibao yako imepokea kiungo kupitia NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Simu yako imepokea kiungo kupitia NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Simu yako imepokea kiungo kupitia NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Kishikwambi chako kimepokea kiungo kupitia NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Fungua kiungo"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Hitilafu ya kusoma NFC. Jaribu tena."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Hakuna programu inayotumika katika Tagi hii ya NFC"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Gusa ili upate maelezo kuhusu jinsi ya kurekebisha."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Data ya NFC inarekodiwa"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Gusa ili usitishe kurekodi."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Ungependa kuruhusu <xliff:g id="PKG">%1$s</xliff:g> iwashe huduma ya NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ndiyo"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Hapana"</string>
</resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index b3cbfff..06a5322 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android பீம்"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"வெளிப்புறச் சேமிப்பகத்திற்கான அனுமதி பயன்பாட்டிற்கு இல்லை. ஃபைலை பீம் செய்ய, இந்த அனுமதி தேவை"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"இணைப்பைத் திறக்கவா?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"NFC வழியாக உங்கள் டேப்லெட்டுக்கு, ஒரு இணைப்பு வந்துள்ளது:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"NFC வழியாக உங்கள் ஃபோனுக்கு, ஒரு இணைப்பு வந்துள்ளது:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"NFC வழியாக உங்கள் ஃபோனுக்கு, ஒரு இணைப்பு வந்துள்ளது:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"NFC வழியாக உங்கள் டேப்லெட்டுக்கு, ஒரு இணைப்பு வந்துள்ளது:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"இணைப்பைத் திற"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC குறியைப் படிக்க இயலவில்லை. பிறகு முயலவும்."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"இந்த NFC குறிக்கு ஏற்ற ஆப்ஸ் இல்லை"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"எப்படி சரிசெய்வது என்பதை அறிய தட்டவும்."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC தரவு ரெக்கார்டு செய்யப்படுகிறது"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ரெக்கார்டிங்கை நிறுத்த தட்டவும்."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFCயை இயக்க <xliff:g id="PKG">%1$s</xliff:g> ஐ அனுமதிக்கவா?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ஆம்"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"இல்லை"</string>
</resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index d985991..fd68b7c 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"అప్లికేషన్ బాహ్య స్టోరేజ్ అనుమతిని కలిగి లేదు. దీనికి ఈ ఫైల్ని బీమ్ చేయడం అవసరమవుతుంది"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"లింక్ని తెరవాలా?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"మీ టాబ్లెట్ NFC ద్వారా లింక్ని పొందింది:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"మీ ఫోన్ NFC ద్వారా లింక్ని పొందింది:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"మీ ఫోన్ NFC ద్వారా లింక్ని పొందింది:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"మీ టాబ్లెట్ NFC ద్వారా లింక్ను పొందింది:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"లింక్ని తెరవండి"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFCని రీడ్ చేయడంలో ఎర్రర్. మళ్లీ ట్రై చేయండి"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"NFC ట్యాగ్కి మద్దతు ఇచ్చే అప్లికేషన్ ఏదీ లేదు"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"ఎలా పరిష్కరించాలో తెలుసుకోవడానికి ట్యాప్ చేయండి."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC డేటా రికార్డ్ అవుతోంది"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"రికార్డింగ్ ఆపడానికి ట్యాప్ చేయండి."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"NFCని ఎనేబుల్ చేయడానికి <xliff:g id="PKG">%1$s</xliff:g>ని అనుమతించాలా?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"అనుమతించండి"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"అనుమతించవద్దు"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 41bfff9..942b399 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"แอปพลิเคชันไม่มีสิทธิ์ใช้พื้นที่เก็บข้อมูลภายนอก ซึ่งเป็นสิทธิ์ที่ต้องใช้ในการบีมไฟล์นี้"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"เปิดลิงก์ไหม"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"แท็บเล็ตได้รับลิงก์ทาง NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"โทรศัพท์ได้รับลิงก์ทาง NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"โทรศัพท์ได้รับลิงก์ทาง NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"แท็บเล็ตได้รับลิงก์ทาง NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"เปิดลิงก์"</string>
<string name="tag_read_error" msgid="2485274498885877547">"เกิดข้อผิดพลาดในการอ่าน NFC โปรดลองอีกครั้ง"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"ไม่มีแอปพลิเคชันที่รองรับแท็ก NFC นี้"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"แตะเพื่อดูวิธีแก้ไข"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"กำลังบันทึกข้อมูล NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"แตะเพื่อหยุดบันทึก"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"อนุญาตให้ <xliff:g id="PKG">%1$s</xliff:g> เปิดใช้ NFC ไหม"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ใช่"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"ไม่"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 24a7fd2..16dbc11 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Walang Pahintulot sa External Storage ang application. Kinakailangan ito upang Ma-beam ang file na ito"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Buksan ang link?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Nakatanggap ng link ang iyong tablet sa pamamagitan ng NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Nakatanggap ng link ang iyong telepono sa pamamagitan ng NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Nakatanggap ng link ang iyong telepono sa pamamagitan ng NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Nakatanggap ng link ang iyong tablet sa pamamagitan ng NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Buksan ang link"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Error sa pagbasa ng NFC. Subukan ulit."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Walang sinusuportahang application para sa Tag ng NFC na ito"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"I-tap para malaman kung paano ayusin."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Nagre-record ng data ng NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"I-tap para ihinto ang pag-record."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Payagan ang <xliff:g id="PKG">%1$s</xliff:g> na i-enable ang NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Oo"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Hindi"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 5df3d45..f92781f 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Uygulamanın Harici Depolama Alanı İzni yok. Dosyanın Işınlanması için bu izin gereklidir."</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Bağlantı açılsın mı?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Tabletiniz NFC üzerinden bir bağlantı aldı:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefonunuz NFC üzerinden bir bağlantı aldı:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefonunuz NFC üzerinden bir bağlantı aldı:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Tabletiniz NFC üzerinden bir bağlantı aldı:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Bağlantıyı aç"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC okuma hatası. Tekrar deneyin."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Bu NFC Etiketi için desteklenen uygulama yok"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Nasıl düzelteceğinizi öğrenmek için dokunun."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC verileri kaydediliyor"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Kaydı durdurmak için dokunun."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> paketinin NFC\'yi etkinleştirmesine izin verilsin mi?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Evet"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Hayır"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 5e8beeb..d799e48 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Передавання даних Android"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Додаток не має доступу до зовнішньої пам’яті. Доступ потрібен для передавання файлу"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Відкрити посилання?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"На планшет надіслано посилання через зв’язок малого радіуса дії (NFC):"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"На телефон надіслано посилання через зв’язок малого радіуса дії (NFC):"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"На телефон надіслано посилання через зв’язок малого радіуса дії (NFC):"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"На планшеті отримано посилання через NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Відкрити посилання"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC-пристрій не розпізнано. Повторіть спробу."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Немає підтримуваного додатка для цієї NFC-мітки"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Натисніть, щоб дізнатися, як це виправити."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Записуються дані NFC"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Натисніть, щоб зупинити записування."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Дозволити <xliff:g id="PKG">%1$s</xliff:g> вмикати NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Так"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Ні"</string>
</resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 9b4852d..9e118dd 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"ایپلیکیشن کو خارجی اسٹوریج کی اجازت نہیں ہے۔ اسے یہ فائل بیم کرنے کی ضرورت ہے"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"لنک کھولیں؟"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"NFC کے ذریعے آپ کے ٹیبلیٹ کو ایک لنک موصول ہوا ہے:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"NFC کے ذریعے آپ کے فون کو ایک لنک موصول ہوا ہے:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"NFC کے ذریعے آپ کے فون کو ایک لنک موصول ہوا ہے:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"NFC کے ذریعے آپ کے ٹیبلیٹ کو ایک لنک موصول ہوا ہے:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"لنک کھولیں"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC پڑھنے میں خرابی۔ دوبارہ کوشش کریں۔"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"اس NFC ٹیگ کے لیے کوئی تعاون یافتہ ایپلیکیشن نہیں ہے"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"درست کرنے کا طریقہ جاننے کے لیے تھپتھپائیں۔"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC ڈیٹا ریکارڈ کیا جا رہا ہے"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"ریکارڈنگ روکنے کے لیے تھپتھپائیں۔"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> کو NFC فعال کرنے کی اجازت دیں؟"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"ہاں"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"نہیں"</string>
</resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 3e2f153..31065f4 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Ilovada tashqi xotira ruxsati yo‘q. Bu ruxsat mazkur faylni uzatish uchun zarur"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Havola ochilsinmi?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Planshetingizga NFC orqali havola keldi:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Telefoningizga NFC orqali havola keldi:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Telefoningizga NFC orqali havola keldi:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Planshetingizga NFC orqali havola keldi:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Havolani ochish"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC tegni o‘qishda xato. Qaytadan urining."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Mazkur NFC yorliq bilan ishlaydigan hech qanday ilova topilmadi"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Muammo yechimini olish uchun bosing."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"NFC maʼlumotlar yozib olinmoqda"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Yozib olishni toʻxtatish uchun bosing."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"<xliff:g id="PKG">%1$s</xliff:g> NFC aloqani yoqishiga ruxsat berilsinmi?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Ha"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Yoʻq"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 9ec960f..4339d90 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Ứng dụng không có quyền truy cập vào Bộ nhớ ngoài. Ứng dụng cần phải có quyền này để chiếu tệp này"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Mở đường liên kết?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Máy tính bảng của bạn đã nhận được một liên kết thông qua NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Điện thoại của bạn đã nhận được một liên kết thông qua NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Điện thoại của bạn đã nhận được một liên kết thông qua NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Máy tính bảng của bạn đã nhận được một liên kết thông qua NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Mở đường liên kết"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Lỗi đọc thẻ NFC. Hãy thử lại."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Không có ứng dụng nào hỗ trợ thẻ NFC này"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Nhấn để tìm hiểu cách khắc phục."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Dữ liệu về NFC đang được ghi lại"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Nhấn để dừng ghi."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Cho phép <xliff:g id="PKG">%1$s</xliff:g> bật NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Có"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Không"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 6468495..87085e5 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"应用不具备外部存储权限。需要此权限才能传输此文件"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"要打开链接吗?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"您的平板电脑已通过 NFC 收到一个链接:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"您的手机已通过 NFC 收到一个链接:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"您的手机已通过 NFC 收到一个链接:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"您的平板电脑通过 NFC 收到了一个链接:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"打开链接"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC 读取错误,请重试。"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"没有应用支持此 NFC 标签"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"点按即可了解如何解决该问题。"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"正在记录 NFC 数据"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"点按即可停止记录。"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"允许“<xliff:g id="PKG">%1$s</xliff:g>”启用 NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"是"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"否"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 2811fe9..4dd1614 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"應用程式沒有「外部儲存空間權限」。你需要此權限才能傳送此檔案"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"要開啟連結嗎?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"你的平板電腦已透過 NFC 收到一個連結:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"你的手機已透過 NFC 收到一個連結:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"你的手機已透過 NFC 收到一個連結:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"你的平板電腦已透過 NFC 收到一個連結:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"開啟連結"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC 讀取錯誤,請再試一次。"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"沒有支援此 NFC 標籤的應用程式"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"輕按即可瞭解如何修正問題。"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"正在記錄 NFC 資料"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"輕按即可停止記錄。"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"要允許「<xliff:g id="PKG">%1$s</xliff:g>」啟用 NFC 嗎?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"是"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"否"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 396c269..f825904 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"應用程式沒有外部儲存空間權限,授予該權限後才能傳輸這個檔案"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"要開啟連結嗎?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"你的平板電腦已透過 NFC 收到 1 個連結:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"你的手機已透過 NFC 收到 1 個連結:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"你的手機已透過 NFC 收到 1 個連結:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"平板電腦已透過 NFC 收到連結:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"開啟連結"</string>
<string name="tag_read_error" msgid="2485274498885877547">"NFC 讀取錯誤,請再試一次。"</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"找不到支援這個 NFC 標記的應用程式"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"輕觸即可瞭解如何修正這個問題。"</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"正在記錄 NFC 資料"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"輕觸即可停止記錄。"</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"要允許「<xliff:g id="PKG">%1$s</xliff:g>」啟用 NFC 嗎?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"是"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"否"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 2d8e23d..3a907de 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -46,8 +46,8 @@
<string name="android_beam" msgid="1666446406999492763">"I-Android Beam"</string>
<string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"Uhlelo lokusebenza alunayo imvume ekusitoreji esingaphandle. Lokhu kuyadingeka ukuze kwenziwe ibhimu leli fayela"</string>
<string name="title_confirm_url_open" msgid="8069968913244794478">"Vula isixhumanisi?"</string>
- <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"Ithebhulethi yakho ithole isixhumanisi nge-NFC:"</string>
- <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"Ifoni yakho ithole isixhumanisi nge-NFC:"</string>
+ <string name="summary_confirm_url_open" msgid="1246398412196449226">"Ifoni yakho ithole isixhumanisi nge-NFC:"</string>
+ <string name="summary_confirm_url_open_tablet" msgid="771152442325809851">"Ithebulethi yakho ithole ilinki nge-NFC:"</string>
<string name="action_confirm_url_open" msgid="3458322738812921189">"Vula isixhumanisi"</string>
<string name="tag_read_error" msgid="2485274498885877547">"Iphutha lokufunda le-NFC. Zama futhi."</string>
<string name="tag_dispatch_failed" msgid="3562984995049738400">"Uhlelo lokusebenza olungasekelwa lale thegi ye-NFC Tag"</string>
@@ -55,4 +55,7 @@
<string name="nfc_blocking_alert_message" msgid="7003156052570107490">"Thepha ukuze ufunde ukuthi ungalungisa kanjani."</string>
<string name="nfc_logging_alert_title" msgid="5300867034660942987">"Idatha ye-NFC iyarekhodwa"</string>
<string name="nfc_logging_alert_message" msgid="1550187184825467942">"Thepha ukuze umise ukurekhoda."</string>
+ <string name="title_package_enabling_nfc" msgid="5736481508428918024">"Vumela i-<xliff:g id="PKG">%1$s</xliff:g> ukunika amandla i-NFC?"</string>
+ <string name="enable_nfc_yes" msgid="694867197186062792">"Yebo"</string>
+ <string name="enable_nfc_no" msgid="6549033065900624599">"Cha"</string>
</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 8d62663..20b7dc3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,6 +19,8 @@
<!-- List of SKUs where Secure NFC functionality is supported -->
<string-array name="config_skuSupportsSecureNfc" translatable="false" />
+ <!-- Set true if Secure NFC functionality supported for all SKUs -->
+ <bool name="enable_secure_nfc_support">false</bool>
<!-- NFC blocking alert notification link uri string -->
<string name="antenna_blocked_alert_link" translatable="false" />
<!-- NFC Antenna Location API -->
@@ -28,4 +30,11 @@
<integer-array name="antenna_x">0</integer-array>
<integer-array name="antenna_y">0</integer-array>
<bool name="nfc_observe_mode_supported">false</bool>
+ <bool name="nfc_proprietary_getcaps_supported">false</bool>
+ <!-- Allow list that contains the package name which are allowed to use NFC -->
+ <string-array name="nfc_allow_list" translatable="false" />
+ <!-- Number of persistent events to store -->
+ <integer name="max_event_log_num">20</integer>
+ <!-- Restart NFC stack when a sim insertion/removal event is detected -->
+ <bool name="restart_on_sim_change">false</bool>
</resources>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index 45b793e..3fbe0af 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -34,6 +34,7 @@
<item name="unknown_tag_polling_delay_count_max" type="integer" />
<item name="unknown_tag_polling_delay_long" type="integer" />
<item name="config_skuSupportsSecureNfc" type="array" />
+ <item name="enable_secure_nfc_support" type="bool" />
<item name="antenna_blocked_alert_link" type="string" />
<item name="device_width" type="integer" />
<item name="device_height" type="integer" />
@@ -41,6 +42,9 @@
<item name="antenna_x" type="array" />
<item name="antenna_y" type="array" />
<item name="nfc_observe_mode_supported" type="bool" />
+ <item name="nfc_proprietary_getcaps_supported" type="bool" />
+ <item name="max_event_log_num" type="integer" />
+ <item name="restart_on_sim_change" type="bool" />
<!-- Params from config.xml that can be overlaid -->
<!-- Params from strings.xml that can be overlaid -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 30983d3..bb42514 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -90,8 +90,9 @@
<!-- Title of the dialog -->
<string name="title_confirm_url_open">Open link?</string>
<!-- Summary text appearing before the URL -->
- <string name="summary_confirm_url_open" product="tablet">Your tablet received a link through NFC:</string>
- <string name="summary_confirm_url_open" product="default">Your phone received a link through NFC:</string>
+ <string name="summary_confirm_url_open">Your phone received a link through NFC:</string>
+ <!-- Summary text appearing before the URL, for tablet -->
+ <string name="summary_confirm_url_open_tablet">Your tablet received a link through NFC:</string>
<!-- Label for the button which causes the URL to be opened -->
<string name="action_confirm_url_open">Open link</string>
@@ -107,4 +108,8 @@
<string name="nfc_logging_alert_title">NFC data is being recorded</string>
<!-- Notification message string informing the user that NFC data is being recorded -->
<string name="nfc_logging_alert_message">Tap to stop recording.</string>
+ <!-- Title of the dialog to allow the package to enable NFC -->
+ <string name="title_package_enabling_nfc">Allow <xliff:g id="pkg">%1$s</xliff:g> to enable NFC?</string>
+ <string name="enable_nfc_yes">Yes</string>
+ <string name="enable_nfc_no">No</string>
</resources>
diff --git a/shim_src/apex/com/android/nfc/TechListChooserActivity.java b/shim_src/apex/com/android/nfc/TechListChooserActivity.java
new file mode 100644
index 0000000..87f87bd
--- /dev/null
+++ b/shim_src/apex/com/android/nfc/TechListChooserActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.android.nfc;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+// Stub for compilation only. Uses #createNfcResolverIntent API instead.
+public class TechListChooserActivity extends Activity {
+ public static final String EXTRA_RESOLVE_INFOS = "rlist";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/src/com/android/nfc/TechListChooserActivity.java b/shim_src/non_apex/com/android/nfc/TechListChooserActivity.java
similarity index 100%
rename from src/com/android/nfc/TechListChooserActivity.java
rename to shim_src/non_apex/com/android/nfc/TechListChooserActivity.java
diff --git a/src/com/android/nfc/ArrayUtils.java b/src/com/android/nfc/ArrayUtils.java
new file mode 100644
index 0000000..1ee5f74
--- /dev/null
+++ b/src/com/android/nfc/ArrayUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.nfc;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
+ */
+public class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if given array is null or has zero elements.
+ */
+ public static boolean isEmpty(@Nullable int[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * True if the byte array is null or has length 0.
+ */
+ public static boolean isEmpty(@Nullable byte[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Converts from List of bytes to byte array
+ * @param list
+ * @return byte[]
+ */
+ public static byte[] toPrimitive(List<byte[]> list) {
+ if (list.size() == 0) {
+ return new byte[0];
+ }
+ int byteLen = list.get(0).length;
+ byte[] array = new byte[list.size() * byteLen];
+ for (int i = 0; i < list.size(); i++) {
+ for (int j = 0; j < list.get(i).length; j++) {
+ array[i * byteLen + j] = list.get(i)[j];
+ }
+ }
+ return array;
+ }
+}
diff --git a/src/com/android/nfc/DeviceHost.java b/src/com/android/nfc/DeviceHost.java
index 7ca13c8..9ae403e 100644
--- a/src/com/android/nfc/DeviceHost.java
+++ b/src/com/android/nfc/DeviceHost.java
@@ -18,10 +18,12 @@
import android.annotation.Nullable;
import android.nfc.NdefMessage;
+import android.nfc.cardemulation.PollingFrame;
import android.os.Bundle;
import java.io.FileDescriptor;
import java.io.IOException;
+import java.util.List;
public interface DeviceHost {
public interface DeviceHostListener {
@@ -43,7 +45,9 @@
public void onHwErrorReported();
- public void onPollingLoopDetected(Bundle pollingFrame);
+ public void onPollingLoopDetected(List<PollingFrame> pollingFrames);
+
+ public void onWlcStopped(int wpt_end_condition);
public void onVendorSpecificEvent(int gid, int oid, byte[] payload);
}
@@ -71,6 +75,7 @@
byte[] readNdef();
boolean writeNdef(byte[] data);
NdefMessage findAndReadNdef();
+ NdefMessage getNdef();
boolean formatNdef(byte[] key);
boolean isNdefFormatable();
boolean makeReadOnly();
@@ -173,11 +178,7 @@
void dump(FileDescriptor fd);
- boolean enableScreenOffSuspend();
-
- boolean disableScreenOffSuspend();
-
- public void doSetScreenState(int screen_state_mask);
+ public void doSetScreenState(int screen_state_mask, boolean alwaysPoll);
public int getNciVersion();
@@ -195,6 +196,8 @@
public boolean setObserveMode(boolean enable);
+ public boolean isObserveModeEnabled();
+
/**
* Get the committed listen mode routing configuration
*/
@@ -220,6 +223,8 @@
*/
boolean setPowerSavingMode(boolean flag);
+ boolean isMultiTag();
+
void setIsoDepProtocolRoute(int route);
void setTechnologyABRoute(int route);
void clearRoutingEntry(int clearFlags);
@@ -233,4 +238,6 @@
* Sends Vendor NCI command
*/
NfcVendorNciResponse sendRawVendorCmd(int mt, int gid, int oid, byte[] payload);
+
+ void enableVendorNciNotifications(boolean enabled);
}
diff --git a/src/com/android/nfc/ForegroundUtils.java b/src/com/android/nfc/ForegroundUtils.java
index ad8d1e3..5d0aa7d 100644
--- a/src/com/android/nfc/ForegroundUtils.java
+++ b/src/com/android/nfc/ForegroundUtils.java
@@ -26,7 +26,7 @@
import androidx.annotation.VisibleForTesting;
public class ForegroundUtils implements ActivityManager.OnUidImportanceListener {
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
private final String TAG = "ForegroundUtils";
private final ActivityManager mActivityManager;
diff --git a/src/com/android/nfc/NfcApplication.java b/src/com/android/nfc/NfcApplication.java
index d2b94e2..eea9d7e 100644
--- a/src/com/android/nfc/NfcApplication.java
+++ b/src/com/android/nfc/NfcApplication.java
@@ -21,6 +21,7 @@
import android.app.Application;
import android.content.pm.PackageManager;
import android.nfc.Constants;
+import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
@@ -63,7 +64,7 @@
}
}
if (UserHandle.myUserId() == 0 && isMainProcess) {
- mNfcService = new NfcService(this);
+ mNfcService = new NfcService(this, new NfcInjector(this, Looper.myLooper()));
}
}
}
diff --git a/src/com/android/nfc/NfcDiagnostics.java b/src/com/android/nfc/NfcDiagnostics.java
new file mode 100644
index 0000000..268dce1
--- /dev/null
+++ b/src/com/android/nfc/NfcDiagnostics.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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 com.android.nfc;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.BugreportManager;
+import android.os.BugreportParams;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * A class to trigger bugreport and other logs for Nfc related failures
+ */
+public class NfcDiagnostics {
+ private static final String TAG = "NfcDiagnostics";
+ private static final int MS_IN_HOUR = 60 * 60 * 1000;
+ public static final int DEFAULT_BUG_REPORT_MIN_INTERVAL_MS = 24 * MS_IN_HOUR;
+
+ private final Context mContext;
+ private final SystemBuildProperties mSystemBuildProperties;
+ private long mLastBugReportTimeMs;
+
+ static class SystemBuildProperties {
+ /** @return if it is an eng build. */
+ public boolean isEngBuild() {
+ return Build.TYPE.equals("eng");
+ }
+
+ /** @return if it is an userdebug build. */
+ public boolean isUserdebugBuild() {
+ return Build.TYPE.equals("userdebug");
+ }
+
+ /** @return if it is a normal user build. */
+ public boolean isUserBuild() {
+ return Build.TYPE.equals("user");
+ }
+ }
+
+ public NfcDiagnostics(Context context) {
+ mContext = context;
+ mSystemBuildProperties = new SystemBuildProperties();
+ }
+
+ /**
+ * Take a bug report if it is in user debug build and there is no recent bug
+ * report
+ */
+ public void takeBugReport(String bugTitle, String description) {
+ if (!mSystemBuildProperties.isUserdebugBuild()) {
+ Log.d(TAG, "Skip bugreport because it can be triggered only in userDebug build");
+ return;
+ }
+ long currentTimeMs = getElapsedSinceBootMillis();
+ long timeSinceLastUploadMs = currentTimeMs - mLastBugReportTimeMs;
+
+ if (timeSinceLastUploadMs < DEFAULT_BUG_REPORT_MIN_INTERVAL_MS
+ && mLastBugReportTimeMs > 0) {
+ Log.d(TAG, "Bugreport was filed recently, Skip " + bugTitle);
+ return;
+ }
+
+ if (!takeBugreportThroughBetterBug(bugTitle, description)) {
+ takeBugreportThroughBugreportManager(bugTitle, description);
+ }
+ }
+
+ private boolean takeBugreportThroughBetterBug(String bugTitle, String description) {
+ Intent launchBetterBugIntent = new BetterBugIntentBuilder()
+ .setIssueTitle(bugTitle)
+ .setHappenedTimestamp(System.currentTimeMillis())
+ .setAdditionalComment(description)
+ .build();
+ boolean isIntentUnSafe = mContext
+ .getPackageManager().queryIntentActivities(launchBetterBugIntent, 0)
+ .isEmpty();
+ if (isIntentUnSafe) {
+ Log.d(TAG, "intent is unsafe and skip bugreport from betterBug: " + bugTitle);
+ return false;
+ }
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ launchBetterBugIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ mContext.startActivity(launchBetterBugIntent);
+ Log.d(TAG, "Taking the bugreport through betterBug: " + bugTitle);
+ mLastBugReportTimeMs = getElapsedSinceBootMillis();
+ return true;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error taking bugreport: " + e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private long getElapsedSinceBootMillis() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ private boolean takeBugreportThroughBugreportManager(String bugTitle, String description) {
+ BugreportManager bugreportManager = mContext.getSystemService(BugreportManager.class);
+ BugreportParams params = new BugreportParams(BugreportParams.BUGREPORT_MODE_FULL);
+ try {
+ bugreportManager.requestBugreport(params, bugTitle, description);
+ Log.d(TAG, "Taking the bugreport through bugreportManager: " + bugTitle);
+ mLastBugReportTimeMs = getElapsedSinceBootMillis();
+ return true;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Error taking bugreport: ", e);
+ return false;
+ }
+ }
+
+ /**
+ * @return the last time when the bug report is taken
+ */
+ long getLastBugReportTimeMs() {
+ return mLastBugReportTimeMs;
+ }
+
+ /**
+ * Builder for communicating with the betterbug.
+ */
+ private static class BetterBugIntentBuilder {
+
+ private static final String ACTION_FILE_BUG_DEEPLINK =
+ "com.google.android.apps.betterbug.intent.FILE_BUG_DEEPLINK";
+ private static final boolean DEFAULT_AUTO_UPLOAD_ENABLED = false;
+ private static final boolean DEFAULT_BUGREPORT_REQUIRED = true;
+ private static final String DEFAULT_BUG_ASSIGNEE = "android-nfc-team@google.com";
+ private static final long DEFAULT_COMPONENT_ID = 48448L;
+
+ private static final String EXTRA_DEEPLINK = "EXTRA_DEEPLINK";
+ private static final String EXTRA_ISSUE_TITLE = "EXTRA_ISSUE_TITLE";
+ private static final String EXTRA_DEEPLINK_SILENT = "EXTRA_DEEPLINK_SILENT";
+ private static final String EXTRA_ADDITIONAL_COMMENT = "EXTRA_ADDITIONAL_COMMENT";
+ private static final String EXTRA_REQUIRE_BUGREPORT = "EXTRA_REQUIRE_BUGREPORT";
+ private static final String EXTRA_HAPPENED_TIME = "EXTRA_HAPPENED_TIME";
+ private static final String EXTRA_BUG_ASSIGNEE = "EXTRA_BUG_ASSIGNEE";
+ private static final String EXTRA_COMPONENT_ID = "EXTRA_COMPONENT_ID";
+
+ private final Intent mBetterBugIntent;
+
+ BetterBugIntentBuilder() {
+ mBetterBugIntent = new Intent().setAction(ACTION_FILE_BUG_DEEPLINK)
+ .putExtra(EXTRA_DEEPLINK, true);
+ setAutoUpload(DEFAULT_AUTO_UPLOAD_ENABLED);
+ setBugreportRequired(DEFAULT_BUGREPORT_REQUIRED);
+ setBugAssignee(DEFAULT_BUG_ASSIGNEE);
+ setComponentId(DEFAULT_COMPONENT_ID);
+ }
+
+ public BetterBugIntentBuilder setIssueTitle(String title) {
+ mBetterBugIntent.putExtra(EXTRA_ISSUE_TITLE, title);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setAutoUpload(boolean autoUploadEnabled) {
+ mBetterBugIntent.putExtra(EXTRA_DEEPLINK_SILENT, autoUploadEnabled);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setComponentId(long componentId) {
+ mBetterBugIntent.putExtra(EXTRA_COMPONENT_ID, componentId);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setBugreportRequired(boolean isBugreportRequired) {
+ mBetterBugIntent.putExtra(EXTRA_REQUIRE_BUGREPORT, isBugreportRequired);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setHappenedTimestamp(long happenedTimeSinceEpochMs) {
+ mBetterBugIntent.putExtra(EXTRA_HAPPENED_TIME, happenedTimeSinceEpochMs);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setAdditionalComment(String additionalComment) {
+ mBetterBugIntent.putExtra(EXTRA_ADDITIONAL_COMMENT, additionalComment);
+ return this;
+ }
+
+ public BetterBugIntentBuilder setBugAssignee(String assignee) {
+ mBetterBugIntent.putExtra(EXTRA_BUG_ASSIGNEE, assignee);
+ return this;
+ }
+
+ public Intent build() {
+ return mBetterBugIntent;
+ }
+ }
+}
diff --git a/src/com/android/nfc/NfcDispatcher.java b/src/com/android/nfc/NfcDispatcher.java
index 2fbabb4..7bd0b35 100644
--- a/src/com/android/nfc/NfcDispatcher.java
+++ b/src/com/android/nfc/NfcDispatcher.java
@@ -16,6 +16,10 @@
package com.android.nfc;
+import static android.content.pm.PackageManager.MATCH_CLONE_PROFILE;
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+import static android.nfc.Flags.enableNfcMainline;
+
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
@@ -47,6 +51,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.NfcProperties;
@@ -79,7 +84,7 @@
*/
class NfcDispatcher {
private static final boolean DBG =
- NfcProperties.debug_enabled().orElse(false);
+ NfcProperties.debug_enabled().orElse(true);
private static final String TAG = "NfcDispatcher";
static final int DISPATCH_SUCCESS = 1;
@@ -149,6 +154,10 @@
super.finalize();
}
+ public synchronized void resetForegroundDispatch() {
+ setForegroundDispatch(null, null, new String[][]{});
+ }
+
public synchronized void setForegroundDispatch(PendingIntent intent,
IntentFilter[] filters, String[][] techLists) {
if (DBG) Log.d(TAG, "Set Foreground Dispatch");
@@ -184,6 +193,27 @@
mProvisioningOnly = false;
}
+ private static Intent createNfcResolverIntent(
+ Intent target,
+ CharSequence title,
+ List<ResolveInfo> resolutionList) {
+ Intent resolverIntent = new Intent(NfcAdapter.ACTION_SHOW_NFC_RESOLVER);
+ resolverIntent.putExtra(Intent.EXTRA_INTENT, target);
+ resolverIntent.putExtra(Intent.EXTRA_TITLE, title);
+ resolverIntent.putParcelableArrayListExtra(
+ NfcAdapter.EXTRA_RESOLVE_INFOS, new ArrayList<>(resolutionList));
+ resolverIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return resolverIntent;
+ }
+
+ private static List<ResolveInfo> queryNfcIntentActivitiesAsUser(
+ PackageManager packageManager, Intent intent, UserHandle uh) {
+ return packageManager.queryIntentActivitiesAsUser(intent,
+ ResolveInfoFlags.of(MATCH_DEFAULT_ONLY | MATCH_CLONE_PROFILE),
+ uh);
+ }
+
/**
* Helper for re-used objects and methods during a single tag dispatch.
*/
@@ -191,7 +221,7 @@
public final Intent intent;
public final Tag tag;
- final Intent rootIntent;
+ Intent rootIntent;
final Uri ndefUri;
final String ndefMimeType;
final PackageManager packageManager;
@@ -258,8 +288,8 @@
boolean status = false;
List<UserHandle> luh = getCurrentActiveUserHandles();
for (UserHandle uh : luh) {
- List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent,
- ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY), uh);
+ List<ResolveInfo> activities = queryNfcIntentActivitiesAsUser(
+ packageManager, intent, uh);;
activities = activities.stream().filter(activity -> activity.activityInfo.exported)
.collect(Collectors.toList());
if (activities.size() > 0) {
@@ -308,10 +338,14 @@
if (muteAppCount > 0) {
if (DBG) Log.d(TAG, "muteAppCount = " + muteAppCount);
if (filtered.size() > 0) {
- rootIntent.setClass(context, TechListChooserActivity.class);
- rootIntent.putExtra(Intent.EXTRA_INTENT, intent);
- rootIntent.putParcelableArrayListExtra(
- TechListChooserActivity.EXTRA_RESOLVE_INFOS, filtered);
+ if (enableNfcMainline()) {
+ rootIntent = createNfcResolverIntent(intent, null, filtered);
+ } else {
+ rootIntent.setClass(context, TechListChooserActivity.class);
+ rootIntent.putExtra(Intent.EXTRA_INTENT, intent);
+ rootIntent.putParcelableArrayListExtra(
+ TechListChooserActivity.EXTRA_RESOLVE_INFOS, filtered);
+ }
}
}
return filtered;
@@ -331,9 +365,8 @@
// to determine if there is an Activity to handle this intent, and base the
// result of off that.
// try current user if there is an Activity to handle this intent
- List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent,
- ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY),
- UserHandle.of(ActivityManager.getCurrentUser()));
+ List<ResolveInfo> activities = queryNfcIntentActivitiesAsUser(
+ packageManager, intent, UserHandle.of(ActivityManager.getCurrentUser()));
activities = activities.stream().filter(activity -> activity.activityInfo.exported)
.collect(Collectors.toList());
if (mIsTagAppPrefSupported) {
@@ -362,8 +395,7 @@
List<UserHandle> userHandles = getCurrentActiveUserHandles();
userHandles.remove(UserHandle.of(ActivityManager.getCurrentUser()));
for (UserHandle uh : userHandles) {
- activities = packageManager.queryIntentActivitiesAsUser(intent,
- ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY), uh);
+ activities = queryNfcIntentActivitiesAsUser(packageManager, intent, uh);
activities = activities.stream().filter(activity -> activity.activityInfo.exported)
.collect(Collectors.toList());
if (mIsTagAppPrefSupported) {
@@ -394,9 +426,8 @@
boolean tryStartActivity(Intent intentToStart) {
// try current user if there is an Activity to handle this intent
- List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
- intentToStart, ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY),
- UserHandle.of(ActivityManager.getCurrentUser()));
+ List<ResolveInfo> activities = queryNfcIntentActivitiesAsUser(
+ packageManager, intentToStart, UserHandle.of(ActivityManager.getCurrentUser()));
activities = activities.stream().filter(activity -> activity.activityInfo.exported)
.collect(Collectors.toList());
if (activities.size() > 0) {
@@ -420,8 +451,7 @@
List<UserHandle> userHandles = getCurrentActiveUserHandles();
userHandles.remove(UserHandle.of(ActivityManager.getCurrentUser()));
for (UserHandle uh : userHandles) {
- activities = packageManager.queryIntentActivitiesAsUser(intentToStart,
- ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY), uh);
+ activities = queryNfcIntentActivitiesAsUser(packageManager, intentToStart, uh);
activities = activities.stream().filter(activity -> activity.activityInfo.exported)
.collect(Collectors.toList());
if (mIsTagAppPrefSupported) {
@@ -920,11 +950,15 @@
dispatch.intent.setComponent(null);
} else if (matches.size() > 1) {
// Multiple matches, show a custom activity chooser dialog
- Intent intent = new Intent(mContext, TechListChooserActivity.class);
- intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
- intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
- matches);
-
+ Intent intent;
+ if (enableNfcMainline()) {
+ intent = createNfcResolverIntent(dispatch.intent, null, matches);
+ } else {
+ intent = new Intent(mContext, TechListChooserActivity.class);
+ intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
+ intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
+ matches);
+ }
if (DBG) Log.i(TAG, "matched multiple TECH");
NfcStatsLog.write(NfcStatsLog.NFC_READER_CONFLICT_OCCURRED);
return dispatch.tryStartActivity(intent);
@@ -1068,6 +1102,11 @@
return enabled;
}
+ private boolean isTablet() {
+ return Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+ .contains("tablet");
+ }
+
boolean showWebLinkConfirmation(DispatchInfo dispatch) {
if (!mContext.getResources().getBoolean(R.bool.enable_nfc_url_open_dialog)) {
return dispatch.tryStartActivity();
@@ -1077,7 +1116,9 @@
R.style.DialogAlertDayNight);
builder.setTitle(R.string.title_confirm_url_open);
LayoutInflater inflater = LayoutInflater.from(mContext);
- View view = inflater.inflate(R.layout.url_open_confirmation, null);
+ View view = inflater.inflate(
+ isTablet() ? R.layout.url_open_confirmation_tablet : R.layout.url_open_confirmation,
+ null);
if (view != null) {
TextView url = view.findViewById(R.id.url_open_confirmation_link);
if (url != null) {
diff --git a/src/com/android/nfc/NfcEnableAllowlistActivity.java b/src/com/android/nfc/NfcEnableAllowlistActivity.java
new file mode 100644
index 0000000..c05da47
--- /dev/null
+++ b/src/com/android/nfc/NfcEnableAllowlistActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 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 com.android.nfc;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.android.nfc.NfcService.APP_NAME_ENABLING_NFC;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+public class NfcEnableAllowlistActivity extends Activity implements View.OnClickListener{
+
+ static final String TAG = "NfcEnableAllowListActivity";
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ Intent intent = getIntent();
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ CharSequence appName = intent.getStringExtra(APP_NAME_ENABLING_NFC);
+ String formatString = getString(R.string.title_package_enabling_nfc);
+ AlertDialog mAlertDialog = new AlertDialog.Builder(this, R.style.DialogAlertDayNight)
+ .setTitle(String.format(formatString, appName))
+ .setNegativeButton(R.string.enable_nfc_no, null)
+ .setPositiveButton(R.string.enable_nfc_yes, null)
+ .create();
+
+ mAlertDialog.show();
+ super.onCreate(savedInstanceState);
+ mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
+ mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(
+ v -> {
+ Log.i(TAG, "Nfc is disallowed by user for app: " + appName);
+ finish();
+ });
+ }
+
+ @Override
+ public void onClick(View v) {
+ NfcService sService = NfcService.getInstance();
+ sService.enableNfc();
+ finish();
+ }
+}
diff --git a/src/com/android/nfc/NfcEventLog.java b/src/com/android/nfc/NfcEventLog.java
new file mode 100644
index 0000000..3b6d151
--- /dev/null
+++ b/src/com/android/nfc/NfcEventLog.java
@@ -0,0 +1,171 @@
+/*
+ * 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 com.android.nfc;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.nfc.proto.NfcEventProto;
+
+import libcore.util.HexEncoding;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
+
+/**
+ * Used to store important NFC event logs persistently for debugging purposes.
+ */
+public final class NfcEventLog {
+ private static final String TAG = "NfcEventLog";
+ @VisibleForTesting
+ public static final DateTimeFormatter FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
+ private final Context mContext;
+ private final NfcInjector mNfcInjector;
+ private final Handler mHander;
+ private final int mMaxEventNum;
+ private final AtomicFile mLogFile;
+ private final ArrayDeque<NfcEventProto.Event> mEventList;
+
+ public NfcEventLog(Context context, NfcInjector nfcInjector, Looper looper,
+ AtomicFile logFile) {
+ mContext = context;
+ mNfcInjector = nfcInjector;
+ mMaxEventNum = context.getResources().getInteger(R.integer.max_event_log_num);
+ mEventList = new ArrayDeque<NfcEventProto.Event>(mMaxEventNum);
+ mHander = new Handler(looper);
+ mLogFile = logFile;
+ mHander.post(() -> readListFromLogFile());
+ }
+
+ private byte[] readLogFile() {
+ byte[] bytes;
+ try {
+ bytes = mLogFile.readFully();
+ } catch (IOException e) {
+ return null;
+ }
+ return bytes;
+ }
+
+ private void writeLogFile(byte[] bytes) throws IOException {
+ FileOutputStream out = null;
+ try {
+ out = mLogFile.startWrite();
+ out.write(bytes);
+ mLogFile.finishWrite(out);
+ } catch (IOException e) {
+ if (out != null) {
+ mLogFile.failWrite(out);
+ }
+ throw e;
+ }
+ }
+
+ private void readListFromLogFile() {
+ byte[] bytes = readLogFile();
+ if (bytes == null) {
+ Log.i(TAG, "No NFC events found in log file");
+ return;
+ }
+ NfcEventProto.EventList eventList;
+ try {
+ eventList = NfcEventProto.EventList.parseFrom(bytes);
+ } catch (InvalidProtocolBufferException e) {
+ Log.e(TAG, "Failed to deserialize events from log file", e);
+ return;
+ }
+ synchronized (mEventList) {
+ for (NfcEventProto.Event event : eventList.getEventsList()) {
+ mEventList.add(event);
+ }
+ }
+ }
+
+ private void writeListToLogFile() {
+ NfcEventProto.EventList.Builder eventListBuilder =
+ NfcEventProto.EventList.newBuilder();
+ synchronized (mEventList) {
+ for (NfcEventProto.Event event: mEventList) {
+ eventListBuilder.addEvents(event);
+ }
+ }
+ byte[] bytes = eventListBuilder.build().toByteArray();
+ Log.d(TAG, "writeListToLogFile: " + HexEncoding.encodeToString(bytes));
+ try {
+ writeLogFile(bytes);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write to log file", e);
+ }
+ }
+
+ private void addAndWriteListToLogFile(NfcEventProto.Event event) {
+ synchronized (mEventList) {
+ // Trim the list to MAX_EVENTS.
+ if (mEventList.size() == mMaxEventNum) {
+ mEventList.remove();
+ }
+ mEventList.add(event);
+ writeListToLogFile();
+ }
+ }
+
+ /**
+ * Log NFC event
+ * Does not block the main NFC thread for logging, posts it to the logging thraead.
+ */
+ public void logEvent(NfcEventProto.EventType eventType) {
+ mHander.post(() -> {
+ NfcEventProto.Event event = NfcEventProto.Event.newBuilder()
+ .setTimestamp(mNfcInjector.getLocalDateTime().format(FORMATTER))
+ .setEventType(eventType)
+ .build();
+ addAndWriteListToLogFile(event);
+ });
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("===== Nfc Event Log =====");
+ synchronized (mEventList) {
+ for (NfcEventProto.Event event: mEventList) {
+ // Cleanup the proto string output to make it more readable.
+ String eventTypeString = event.getEventType().toString()
+ .replaceAll("# com.android.nfc.proto.*", "")
+ .replaceAll("\n", "");
+ pw.println(event.getTimestamp() + ": " + eventTypeString);
+ }
+ }
+ pw.println("===== Nfc Event Log =====");
+ }
+
+ @VisibleForTesting
+ public ArrayDeque<NfcEventProto.Event> getEventsList() {
+ return mEventList;
+ }
+}
diff --git a/src/com/android/nfc/NfcInjector.java b/src/com/android/nfc/NfcInjector.java
new file mode 100644
index 0000000..f1a5d06
--- /dev/null
+++ b/src/com/android/nfc/NfcInjector.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.android.nfc;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.backup.BackupManager;
+import android.content.ApexEnvironment;
+import android.content.Context;
+import android.content.res.Resources;
+import android.nfc.Constants;
+import android.nfc.NfcFrameworkInitializer;
+import android.nfc.NfcServiceManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.VibrationEffect;
+import android.provider.Settings;
+import android.se.omapi.ISecureElementService;
+import android.se.omapi.SeFrameworkInitializer;
+import android.se.omapi.SeServiceManager;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import com.android.nfc.cardemulation.util.StatsdUtils;
+import com.android.nfc.dhimpl.NativeNfcManager;
+import com.android.nfc.flags.FeatureFlags;
+import com.android.nfc.handover.HandoverDataParser;
+import com.android.nfc.proto.NfcEventProto;
+
+import java.io.File;
+import java.time.LocalDateTime;
+
+/**
+ * To be used for dependency injection (especially helps mocking static dependencies).
+ * TODO: Migrate more classes to injector to resolve circular dependencies in the NFC stack.
+ */
+public class NfcInjector {
+ private static final String TAG = "NfcInjector";
+ private static final String APEX_NAME = "com.android.nfcservices";
+ private static final String NFC_DATA_DIR = "/data/nfc";
+ private static final String EVENT_LOG_FILE_NAME = "event_log.binpb";
+
+ private final Context mContext;
+ private final Looper mMainLooper;
+ private final NfcEventLog mNfcEventLog;
+ private final RoutingTableParser mRoutingTableParser;
+ private final ScreenStateHelper mScreenStateHelper;
+ private final NfcUnlockManager mNfcUnlockManager;
+ private final HandoverDataParser mHandoverDataParser;
+ private final DeviceConfigFacade mDeviceConfigFacade;
+ private final NfcDispatcher mNfcDispatcher;
+ private final VibrationEffect mVibrationEffect;
+ private final BackupManager mBackupManager;
+ private final FeatureFlags mFeatureFlags;
+ private final StatsdUtils mStatsdUtils;
+ private final ForegroundUtils mForegroundUtils;
+ private final NfcDiagnostics mNfcDiagnostics;
+ private final NfcServiceManager.ServiceRegisterer mNfcManagerRegisterer;
+ private static NfcInjector sInstance;
+
+ public static NfcInjector getInstance() {
+ if (sInstance == null) throw new IllegalStateException("Nfc injector instance null");
+ return sInstance;
+ }
+
+
+ public NfcInjector(@NonNull Context context, @NonNull Looper mainLooper) {
+ if (sInstance != null) throw new IllegalStateException("Nfc injector instance not null");
+
+ mContext = context;
+ mMainLooper = mainLooper;
+ mRoutingTableParser = new RoutingTableParser();
+ mScreenStateHelper = new ScreenStateHelper(mContext);
+ mNfcUnlockManager = NfcUnlockManager.getInstance();
+ mHandoverDataParser = new HandoverDataParser();
+ mDeviceConfigFacade = new DeviceConfigFacade(mContext, new Handler(mainLooper));
+ mNfcDispatcher = new NfcDispatcher(mContext, mHandoverDataParser, isInProvisionMode());
+ mVibrationEffect = VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE);
+ mBackupManager = new BackupManager(mContext);
+ mFeatureFlags = new com.android.nfc.flags.FeatureFlagsImpl();
+ mStatsdUtils = mFeatureFlags.statsdCeEventsFlag() ? new StatsdUtils() : null;
+ mForegroundUtils =
+ ForegroundUtils.getInstance(mContext.getSystemService(ActivityManager.class));
+ mNfcDiagnostics = new NfcDiagnostics(mContext);
+
+ NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
+ if (manager == null) {
+ Log.e(TAG, "NfcServiceManager is null");
+ throw new UnsupportedOperationException();
+ }
+ mNfcManagerRegisterer = manager.getNfcManagerServiceRegisterer();
+
+ // Create UWB event log thread.
+ HandlerThread eventLogThread = new HandlerThread("NfcEventLog");
+ eventLogThread.start();
+ mNfcEventLog = new NfcEventLog(mContext, this, eventLogThread.getLooper(),
+ new AtomicFile(new File(NFC_DATA_DIR, EVENT_LOG_FILE_NAME)));
+ sInstance = this;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Looper getMainLooper() {
+ return mMainLooper;
+ }
+
+ public NfcEventLog getNfcEventLog() {
+ return mNfcEventLog;
+ }
+
+ public ScreenStateHelper getScreenStateHelper() {
+ return mScreenStateHelper;
+ }
+
+ public RoutingTableParser getRoutingTableParser() {
+ return mRoutingTableParser;
+ }
+
+ public NfcUnlockManager getNfcUnlockManager() {
+ return mNfcUnlockManager;
+ }
+
+ public HandoverDataParser getHandoverDataParser() {
+ return mHandoverDataParser;
+ }
+
+ public DeviceConfigFacade getDeviceConfigFacade() {
+ return mDeviceConfigFacade;
+ }
+
+ public NfcDispatcher getNfcDispatcher() {
+ return mNfcDispatcher;
+ }
+
+ public VibrationEffect getVibrationEffect() {
+ return mVibrationEffect;
+ }
+
+ public BackupManager getBackupManager() {
+ return mBackupManager;
+ }
+
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
+ public StatsdUtils getStatsdUtils() {
+ return mStatsdUtils;
+ }
+
+ public ForegroundUtils getForegroundUtils() {
+ return mForegroundUtils;
+ }
+
+ public NfcDiagnostics getNfcDiagnostics() {
+ return mNfcDiagnostics;
+ }
+
+ public NfcServiceManager.ServiceRegisterer getNfcManagerRegisterer() {
+ return mNfcManagerRegisterer;
+ }
+
+ public DeviceHost makeDeviceHost(DeviceHost.DeviceHostListener listener) {
+ return new NativeNfcManager(mContext, listener);
+ }
+
+ /**
+ * NFC apex DE folder.
+ */
+ public static File getDeviceProtectedDataDir() {
+ return ApexEnvironment.getApexEnvironment(APEX_NAME)
+ .getDeviceProtectedDataDir();
+ }
+
+ public LocalDateTime getLocalDateTime() {
+ return LocalDateTime.now();
+ }
+
+ public boolean isInProvisionMode() {
+ boolean isNfcProvisioningEnabled = false;
+ try {
+ isNfcProvisioningEnabled = mContext.getResources().getBoolean(
+ R.bool.enable_nfc_provisioning);
+ } catch (Resources.NotFoundException e) {
+ }
+
+ if (isNfcProvisioningEnabled) {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean checkIsSecureNfcCapable() {
+ if (mContext.getResources().getBoolean(R.bool.enable_secure_nfc_support)) {
+ return true;
+ }
+ String[] skuList = mContext.getResources().getStringArray(
+ R.array.config_skuSupportsSecureNfc);
+ String sku = SystemProperties.get("ro.boot.hardware.sku");
+ if (TextUtils.isEmpty(sku) || !Utils.arrayContains(skuList, sku)) {
+ return false;
+ }
+ return true;
+ }
+
+ public ISecureElementService connectToSeService() throws RemoteException {
+ SeServiceManager manager = SeFrameworkInitializer.getSeServiceManager();
+ if (manager == null) {
+ Log.e(TAG, "SEServiceManager is null");
+ return null;
+ }
+ return ISecureElementService.Stub.asInterface(
+ manager.getSeManagerServiceRegisterer().get());
+ }
+
+ /**
+ * Kill the NFC stack.
+ */
+ public void killNfcStack() {
+ System.exit(0);
+ }
+
+ public boolean isSatelliteModeSensitive() {
+ final String satelliteRadios =
+ Settings.Global.getString(mContext.getContentResolver(),
+ Constants.SETTINGS_SATELLITE_MODE_RADIOS);
+ return satelliteRadios == null || satelliteRadios.contains(Settings.Global.RADIO_NFC);
+ }
+
+ /** Returns true if satellite mode is turned on. */
+ public boolean isSatelliteModeOn() {
+ if (!isSatelliteModeSensitive()) return false;
+ return Settings.Global.getInt(
+ mContext.getContentResolver(), Constants.SETTINGS_SATELLITE_MODE_ENABLED, 0) == 1;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/nfc/NfcPermissions.java b/src/com/android/nfc/NfcPermissions.java
index 4d43ef6..c88b667 100644
--- a/src/com/android/nfc/NfcPermissions.java
+++ b/src/com/android/nfc/NfcPermissions.java
@@ -1,5 +1,7 @@
package com.android.nfc;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
import android.app.ActivityManager;
import android.content.Context;
import android.os.Binder;
@@ -67,6 +69,9 @@
context.enforceCallingOrSelfPermission(ADMIN_PERM, ADMIN_PERM_ERROR);
}
+ public static boolean checkAdminPermissions(Context context) {
+ return context.checkCallingPermission(ADMIN_PERM) == PERMISSION_GRANTED;
+ }
public static void enforceUserPermissions(Context context) {
context.enforceCallingOrSelfPermission(NFC_PERMISSION, NFC_PERM_ERROR);
diff --git a/src/com/android/nfc/NfcProprietaryCaps.java b/src/com/android/nfc/NfcProprietaryCaps.java
new file mode 100644
index 0000000..d9890ed
--- /dev/null
+++ b/src/com/android/nfc/NfcProprietaryCaps.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.android.nfc;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class NfcProprietaryCaps {
+ private static final String TAG = "NfcProprietaryCaps";
+ private static final int PASSIVE_OBSERVE_MODE = 0;
+ private static final int POLLING_FRAME_NTF = 1;
+ private static final int POWER_SAVING_MODE = 2;
+ private static final int AUTOTRANSACT_POLLING_LOOP_FILTER = 3;
+ private final PassiveObserveMode mPassiveObserveMode;
+ private final boolean mIsPollingFrameNotificationSupported;
+ private final boolean mIsPowerSavingModeSupported;
+ private final boolean mIsAutotransactPollingLoopFilterSupported;
+
+ public enum PassiveObserveMode {
+ NOT_SUPPORTED,
+ SUPPORT_WITH_RF_DEACTIVATION,
+ SUPPORT_WITHOUT_RF_DEACTIVATION,
+ }
+
+ public PassiveObserveMode getPassiveObserveMode() {
+ return mPassiveObserveMode;
+ }
+
+ public boolean isPollingFrameNotificationSupported() {
+ return mIsPollingFrameNotificationSupported;
+ }
+
+ public boolean isPowerSavingModeSupported() {
+ return mIsPowerSavingModeSupported;
+ }
+
+ public boolean isAutotransactPollingLoopFilterSupported() {
+ return mIsAutotransactPollingLoopFilterSupported;
+ }
+
+ public NfcProprietaryCaps(PassiveObserveMode passiveObserveMode,
+ boolean isPollingFrameNotificationSupported, boolean isPowerSavingModeSupported,
+ boolean isAutotransactPollingLoopFilterSupported) {
+ mPassiveObserveMode = passiveObserveMode;
+ mIsPollingFrameNotificationSupported = isPollingFrameNotificationSupported;
+ mIsPowerSavingModeSupported = isPowerSavingModeSupported;
+ mIsAutotransactPollingLoopFilterSupported = isAutotransactPollingLoopFilterSupported;
+ }
+
+ public static NfcProprietaryCaps createFromByteArray(byte[] caps) {
+ Log.i(TAG, "parsing proprietary caps: " + Arrays.toString(caps));
+ PassiveObserveMode passiveObserveMode = PassiveObserveMode.NOT_SUPPORTED;
+ boolean isPollingFrameNotificationSupported = false;
+ boolean isPowerSavingModeSupported = false;
+ boolean isAutotransactPollingLoopFilterSupported = false;
+ int offset = 0;
+ while ((offset + 2) < caps.length) {
+ int id = caps[offset++];
+ int value_len = caps[offset++];
+ int value_offset = offset;
+ offset += value_len;
+
+ // value bounds check
+ // all caps have minimum length of 1, check this bound
+ // here to simplify match cases.
+ if (value_len < 1 || offset > caps.length) {
+ break;
+ }
+ switch (id) {
+ case PASSIVE_OBSERVE_MODE:
+ passiveObserveMode = switch (caps[value_offset]) {
+ case 0 -> PassiveObserveMode.NOT_SUPPORTED;
+ case 1 -> PassiveObserveMode.SUPPORT_WITH_RF_DEACTIVATION;
+ case 2 -> PassiveObserveMode.SUPPORT_WITHOUT_RF_DEACTIVATION;
+ default -> passiveObserveMode;
+ };
+ break;
+ case POLLING_FRAME_NTF:
+ isPollingFrameNotificationSupported = caps[value_offset] == 0x1;
+ break;
+ case POWER_SAVING_MODE:
+ isPowerSavingModeSupported = caps[value_offset] == 0x1;
+ break;
+ case AUTOTRANSACT_POLLING_LOOP_FILTER:
+ isAutotransactPollingLoopFilterSupported = caps[value_offset] == 0x1;
+ break;
+ }
+ }
+ return new NfcProprietaryCaps(passiveObserveMode, isPollingFrameNotificationSupported,
+ isPowerSavingModeSupported, isAutotransactPollingLoopFilterSupported);
+ }
+}
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index 6e322e5..74fcce2 100644
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -16,6 +16,11 @@
package com.android.nfc;
+import static com.android.nfc.NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__FOREGROUND_APP;
+import static com.android.nfc.NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__TRIGGER_SOURCE_UNKNOWN;
+import static com.android.nfc.NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__WALLET_ROLE_HOLDER;
+
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.Application;
import android.app.BroadcastOptions;
@@ -23,8 +28,8 @@
import android.app.KeyguardManager.KeyguardLockedStateListener;
import android.app.PendingIntent;
import android.app.VrManager;
-import android.app.admin.DevicePolicyManager;
import android.app.backup.BackupManager;
+import android.app.role.RoleManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -42,9 +47,6 @@
import android.net.Uri;
import android.nfc.AvailableNfcAntenna;
import android.nfc.Constants;
-import android.nfc.NfcFrameworkInitializer;
-import android.nfc.NfcServiceManager;
-import android.nfc.cardemulation.CardEmulation;
import android.nfc.ErrorCodes;
import android.nfc.FormatException;
import android.nfc.IAppCallback;
@@ -52,19 +54,25 @@
import android.nfc.INfcAdapterExtras;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcControllerAlwaysOnListener;
-import android.nfc.INfcVendorNciCallback;
import android.nfc.INfcDta;
import android.nfc.INfcFCardEmulation;
import android.nfc.INfcOemExtensionCallback;
import android.nfc.INfcTag;
import android.nfc.INfcUnlockHandler;
+import android.nfc.INfcVendorNciCallback;
+import android.nfc.INfcWlcStateListener;
import android.nfc.ITagRemovedCallback;
import android.nfc.NdefMessage;
import android.nfc.NfcAdapter;
import android.nfc.NfcAntennaInfo;
+import android.nfc.NfcFrameworkInitializer;
+import android.nfc.NfcServiceManager;
import android.nfc.Tag;
import android.nfc.TechListParcel;
import android.nfc.TransceiveResult;
+import android.nfc.WlcListenerDeviceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.PollingFrame;
import android.nfc.tech.Ndef;
import android.nfc.tech.TagTechnology;
import android.os.AsyncTask;
@@ -73,11 +81,13 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
+import android.os.PowerManager.OnThermalStatusChangedListener;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -91,6 +101,7 @@
import android.se.omapi.SeFrameworkInitializer;
import android.se.omapi.SeServiceManager;
import android.sysprop.NfcProperties;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
@@ -104,8 +115,9 @@
import com.android.nfc.cardemulation.util.StatsdUtils;
import com.android.nfc.dhimpl.NativeNfcManager;
import com.android.nfc.flags.FeatureFlags;
-import com.android.nfc.Utils;
import com.android.nfc.handover.HandoverDataParser;
+import com.android.nfc.proto.NfcEventProto;
+import com.android.nfc.wlc.NfcCharging;
import org.json.JSONException;
import org.json.JSONObject;
@@ -124,25 +136,24 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.HexFormat;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
public class NfcService implements DeviceHostListener, ForegroundUtils.Callback {
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
+ private static final boolean VDBG = false; // turn on for local testing.
static final String TAG = "NfcService";
private static final int APP_INFO_FLAGS_SYSTEM_APP =
ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -160,6 +171,9 @@
static final String PREF_NFC_READER_OPTION_ON = "nfc_reader_on";
static final boolean NFC_READER_OPTION_DEFAULT = true;
+ static final String PREF_NFC_CHARGING_ON = "nfc_charging_on";
+ static final boolean NFC_CHARGING_ON_DEFAULT = true;
+
static final String PREF_SECURE_NFC_ON = "secure_nfc_on";
static final boolean SECURE_NFC_ON_DEFAULT = false;
static final String PREF_FIRST_BOOT = "first_boot";
@@ -244,6 +258,9 @@
public static final String ACTION_RF_FIELD_OFF_DETECTED =
"com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED";
+ public static final String APP_NAME_ENABLING_NFC =
+ "com.android.nfc.PACKAGE_NAME_ENABLING_NFC";
+
public static boolean sIsShortRecordLayout = false;
public static boolean sIsNfcRestore = false;
@@ -272,6 +289,7 @@
private static final int NCI_MSG_PROP_ANDROID = 0x0C;
private static final int NCI_MSG_PROP_ANDROID_POWER_SAVING = 0x01;
+ private final Looper mLooper;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
@@ -350,6 +368,8 @@
boolean mIsReaderOptionEnabled = true;
boolean mReaderOptionCapable;
Context mContext;
+ NfcInjector mNfcInjector;
+ NfcEventLog mNfcEventLog;
private DeviceHost mDeviceHost;
private SharedPreferences mPrefs;
private SharedPreferences.Editor mPrefsEditor;
@@ -374,6 +394,12 @@
boolean mIsRecovering;
boolean mIsNfcUserRestricted;
boolean mIsWatchType;
+ boolean mPendingPowerStateUpdate;
+ boolean mIsWlcCapable;
+ boolean mIsWlcEnabled;
+ boolean mIsRWCapable;
+ WlcListenerDeviceInfo mWlcListenerDeviceInfo;
+ public NfcDiagnostics mNfcDiagnostics;
// polling delay control variables
private final int mPollDelayTime;
@@ -395,6 +421,7 @@
private HandoverDataParser mHandoverDataParser;
private ContentResolver mContentResolver;
private CardEmulationManager mCardEmulationManager;
+ private NfcCharging mNfcCharging;
private Vibrator mVibrator;
private VibrationEffect mVibrationEffect;
private ISecureElementService mSEService;
@@ -416,8 +443,9 @@
private final Set<INfcControllerAlwaysOnListener> mAlwaysOnListeners =
Collections.synchronizedSet(new HashSet<>());
- private final FeatureFlags mFeatureFlags = new com.android.nfc.flags.FeatureFlagsImpl();
-
+ private final FeatureFlags mFeatureFlags;
+ private final Set<INfcWlcStateListener> mWlcStateListener =
+ Collections.synchronizedSet(new HashSet<>());
private final StatsdUtils mStatsdUtils;
private INfcVendorNciCallback mNfcVendorNciCallBack = null;
@@ -459,17 +487,25 @@
@Override
public void onRemoteFieldActivated() {
sendMessage(NfcService.MSG_RF_FIELD_ACTIVATED, null);
+
+ if (mStatsdUtils != null) {
+ mStatsdUtils.logFieldChanged(true, 0);
+ }
}
@Override
public void onRemoteFieldDeactivated() {
sendMessage(NfcService.MSG_RF_FIELD_DEACTIVATED, null);
+
+ if (mStatsdUtils != null) {
+ mStatsdUtils.logFieldChanged(false, 0);
+ }
}
@Override
- public void onPollingLoopDetected(Bundle pollingFrame) {
- if (mCardEmulationManager != null) {
- mCardEmulationManager.onPollingLoopDetected(pollingFrame);
+ public void onPollingLoopDetected(List<PollingFrame> frames) {
+ if (mCardEmulationManager != null && android.nfc.Flags.nfcReadPollingLoop()) {
+ mCardEmulationManager.onPollingLoopDetected(frames);
}
}
@@ -484,8 +520,7 @@
new ApplyRoutingTask().execute();
}
- @Override
- public void onHwErrorReported() {
+ private void restartStack() {
try {
mContext.unregisterReceiver(mReceiver);
} catch (IllegalArgumentException e) {
@@ -497,8 +532,13 @@
}
@Override
+ public void onHwErrorReported() {
+ restartStack();
+ }
+
+ @Override
public void onVendorSpecificEvent(int gid, int oid, byte[] payload) {
- sendVendorNciNotification(gid, oid, payload);
+ mHandler.post(() -> mNfcAdapter.sendVendorNciNotification(gid, oid, payload));
}
/**
@@ -529,6 +569,33 @@
return false;
}
+ public void onWlcData(Map<String, Integer> WlcDeviceInfo) {
+ for (String key : WlcDeviceInfo.keySet()) {
+ Log.d(TAG, " onWlcData " + key + " = " + WlcDeviceInfo.get(key));
+ }
+ synchronized (mWlcStateListener) {
+ mWlcListenerDeviceInfo = new WlcListenerDeviceInfo(
+ WlcDeviceInfo.get(mNfcCharging.VendorId),
+ WlcDeviceInfo.get(mNfcCharging.TemperatureListener),
+ WlcDeviceInfo.get(mNfcCharging.BatteryLevel),
+ WlcDeviceInfo.get(mNfcCharging.State));
+ for (INfcWlcStateListener listener : mWlcStateListener) {
+ try {
+ listener.onWlcStateChanged(mWlcListenerDeviceInfo);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onWlcData");
+ }
+ }
+ }
+ }
+
+ /** Notifies WLC procedure stopped */
+ @Override
+ public void onWlcStopped(int wpt_end_condition) {
+ Log.d(TAG, "onWlcStopped() - End condition is " + wpt_end_condition);
+ mNfcCharging.onWlcStopped(wpt_end_condition);
+ }
+
final class ReaderModeParams {
public int flags;
public IAppCallback callback;
@@ -556,28 +623,6 @@
}
}
- /**
- * @hide constant copied from {@link Settings.Global}
- * TODO(b/274636414): Migrate to official API in Android V.
- */
- private 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.
- */
- private static final String SETTINGS_SATELLITE_MODE_ENABLED = "satellite_mode_enabled";
-
- private boolean isSatelliteModeSensitive() {
- final String satelliteRadios =
- Settings.Global.getString(mContentResolver, SETTINGS_SATELLITE_MODE_RADIOS);
- return satelliteRadios == null || satelliteRadios.contains(Settings.Global.RADIO_NFC);
- }
-
- /** Returns true if satellite mode is turned on. */
- private boolean isSatelliteModeOn() {
- if (!isSatelliteModeSensitive()) return false;
- return Settings.Global.getInt(mContentResolver, SETTINGS_SATELLITE_MODE_ENABLED, 0) == 1;
- }
/** Returns true if NFC has user restriction set. */
private boolean isNfcUserRestricted() {
@@ -586,43 +631,48 @@
}
boolean shouldEnableNfc() {
- return getNfcOnSetting() && !isSatelliteModeOn() && !isNfcUserRestricted();
+ return getNfcOnSetting() && !mNfcInjector.isSatelliteModeOn() && !isNfcUserRestricted();
}
- public NfcService(Application nfcApplication) {
+ private void registerGlobalBroadcastsReceiver() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_USER_PRESENT);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_USER_ADDED);
+ if (mContext.getResources().getBoolean(R.bool.restart_on_sim_change)) {
+ filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+ }
+ mContext.registerReceiverForAllUsers(mReceiver, filter, null, null);
+ }
+
+ public NfcService(Application nfcApplication, NfcInjector nfcInjector) {
mUserId = ActivityManager.getCurrentUser();
mContext = nfcApplication;
+ mNfcInjector = nfcInjector;
+ mLooper = mNfcInjector.getMainLooper();
+ mHandler = new NfcServiceHandler(mLooper);
+ mNfcEventLog = mNfcInjector.getNfcEventLog();
mNfcTagService = new TagService();
mNfcAdapter = new NfcAdapterService();
- mRoutingTableParser = new RoutingTableParser();
+ mRoutingTableParser = mNfcInjector.getRoutingTableParser();
Log.i(TAG, "Starting NFC service");
sService = this;
- mScreenStateHelper = new ScreenStateHelper(mContext);
+ mScreenStateHelper = mNfcInjector.getScreenStateHelper();
mContentResolver = mContext.getContentResolver();
- mDeviceHost = new NativeNfcManager(mContext, this);
+ mDeviceHost = mNfcInjector.makeDeviceHost(this);
- mNfcUnlockManager = NfcUnlockManager.getInstance();
+ mNfcUnlockManager = mNfcInjector.getNfcUnlockManager();
- mHandoverDataParser = new HandoverDataParser();
- boolean isNfcProvisioningEnabled = false;
- try {
- isNfcProvisioningEnabled = mContext.getResources().getBoolean(
- R.bool.enable_nfc_provisioning);
- } catch (NotFoundException e) {
- }
+ mHandoverDataParser = mNfcInjector.getHandoverDataParser();
+ mInProvisionMode = mNfcInjector.isInProvisionMode();
+ mDeviceConfigFacade = mNfcInjector.getDeviceConfigFacade();
- if (isNfcProvisioningEnabled) {
- mInProvisionMode = Settings.Global.getInt(mContentResolver,
- Settings.Global.DEVICE_PROVISIONED, 0) == 0;
- } else {
- mInProvisionMode = false;
- }
- mDeviceConfigFacade = new DeviceConfigFacade(mContext, mHandler);
-
- mNfcDispatcher = new NfcDispatcher(mContext, mHandoverDataParser, mInProvisionMode);
+ mNfcDispatcher = mNfcInjector.getNfcDispatcher();
mPrefs = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE);
mPrefsEditor = mPrefs.edit();
@@ -645,11 +695,13 @@
mUserManager = mContext.getSystemService(UserManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
mVibrator = mContext.getSystemService(Vibrator.class);
- mVibrationEffect = VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE);
+ mVibrationEffect = mNfcInjector.getVibrationEffect();
PackageManager pm = mContext.getPackageManager();
mIsWatchType = pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ mNfcDiagnostics = mNfcInjector.getNfcDiagnostics();
+
if (pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE) &&
!mIsWatchType) {
mVrManager = mContext.getSystemService(VrManager.class);
@@ -659,17 +711,13 @@
mScreenState = mScreenStateHelper.checkScreenState();
- mBackupManager = new BackupManager(mContext);
+ mBackupManager = mNfcInjector.getBackupManager();
- mStatsdUtils = mFeatureFlags.statsdCeEventsFlag() ? new StatsdUtils() : null;
+ mFeatureFlags = mNfcInjector.getFeatureFlags();
+ mStatsdUtils = mNfcInjector.getStatsdUtils();
// Intents for all users
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_ADDED);
- mContext.registerReceiverForAllUsers(mReceiver, filter, null, null);
+ registerGlobalBroadcastsReceiver();
// Listen for work profile adds or removes.
IntentFilter managedProfileFilter = new IntentFilter();
@@ -695,6 +743,16 @@
updatePackageCache();
+ mIsRWCapable = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+ mIsWlcCapable = android.nfc.Flags.enableNfcCharging() &&
+ pm.hasSystemFeature(PackageManager.FEATURE_NFC_CHARGING);
+ if (mIsWlcCapable) {
+ mNfcCharging = new NfcCharging(mContext, mDeviceHost);
+ mIsWlcEnabled = mPrefs.getBoolean(PREF_NFC_CHARGING_ON, NFC_CHARGING_ON_DEFAULT);
+ // Register ThermalStatusChangedListener
+ addThermalStatusListener();
+ }
+
mIsHceCapable =
pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION) ||
pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
@@ -703,9 +761,8 @@
if (mIsHceCapable) {
mCardEmulationManager = new CardEmulationManager(mContext);
}
- mForegroundUtils = ForegroundUtils.getInstance(mActivityManager);
-
- mIsSecureNfcCapable = mNfcAdapter.deviceSupportsNfcSecure();
+ mForegroundUtils = mNfcInjector.getForegroundUtils();
+ mIsSecureNfcCapable = mNfcInjector.checkIsSecureNfcCapable();
mIsSecureNfcEnabled =
mPrefs.getBoolean(PREF_SECURE_NFC_ON, SECURE_NFC_ON_DEFAULT) &&
mIsSecureNfcCapable;
@@ -743,12 +800,7 @@
mPollingDisableAllowed = mContext.getResources().getBoolean(R.bool.polling_disable_allowed);
// Make sure this is only called when object construction is complete.
- NfcServiceManager manager = NfcFrameworkInitializer.getNfcServiceManager();
- if (manager == null) {
- Log.e(TAG, "NfcServiceManager is null");
- throw new UnsupportedOperationException();
- }
- manager.getNfcManagerServiceRegisterer().register(mNfcAdapter);
+ mNfcInjector.getNfcManagerRegisterer().register(mNfcAdapter);
mIsAlwaysOnSupported =
mContext.getResources().getBoolean(R.bool.nfcc_always_on_allowed);
@@ -756,7 +808,7 @@
mIsTagAppPrefSupported =
mContext.getResources().getBoolean(R.bool.tag_intent_app_pref_supported);
- Uri uri = Settings.Global.getUriFor(SETTINGS_SATELLITE_MODE_ENABLED);
+ Uri uri = Settings.Global.getUriFor(Constants.SETTINGS_SATELLITE_MODE_ENABLED);
if (uri == null) {
Log.e(TAG, "satellite mode key does not exist in Settings");
} else {
@@ -766,7 +818,7 @@
new ContentObserver(null) {
@Override
public void onChange(boolean selfChange) {
- if (isSatelliteModeSensitive()) {
+ if (mNfcInjector.isSatelliteModeSensitive()) {
Log.i(TAG, "Satellite mode change detected");
if (shouldEnableNfc()) {
new EnableDisableTask().execute(TASK_ENABLE);
@@ -920,13 +972,7 @@
private void connectToSeService() {
try {
- SeServiceManager manager = SeFrameworkInitializer.getSeServiceManager();
- if (manager == null) {
- Log.e(TAG, "SEServiceManager is null");
- return;
- }
- mSEService = ISecureElementService.Stub.asInterface(
- manager.getSeManagerServiceRegisterer().get());
+ mSEService = mNfcInjector.connectToSeService();
if (mSEService != null) {
IBinder seServiceBinder = mSEService.asBinder();
seServiceBinder.linkToDeath(mSeServiceDeathRecipient, 0);
@@ -1069,6 +1115,7 @@
disableInternal();
break;
case TASK_BOOT:
+ // Initialize the event log cache.
boolean initialized;
if (mPrefs.getBoolean(PREF_FIRST_BOOT, true)) {
Log.i(TAG, "First Boot");
@@ -1078,13 +1125,20 @@
setPaymentForegroundPreference(mUserId);
}
Log.d(TAG, "checking on firmware download");
- if (shouldEnableNfc()) {
+ boolean enableNfc = shouldEnableNfc();
+ if (enableNfc) {
Log.d(TAG, "NFC is on. Doing normal stuff");
initialized = enableInternal();
} else {
Log.d(TAG, "NFC is off. Checking firmware version");
initialized = mDeviceHost.checkFirmware();
}
+ mNfcEventLog.logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setBootupState(NfcEventProto.NfcBootupState.newBuilder()
+ .setEnabled(enableNfc)
+ .build())
+ .build());
if (initialized) {
// TODO(279846422) The system property will be temporary
// available for vendors that depend on it.
@@ -1165,6 +1219,8 @@
nci_version = getNciVersion();
Log.d(TAG, "NCI_Version: " + nci_version);
+ mPendingPowerStateUpdate = false;
+
synchronized (NfcService.this) {
mObjectMap.clear();
@@ -1182,7 +1238,7 @@
if(mNfcUnlockManager.isLockscreenPollingEnabled())
applyRouting(false);
- mDeviceHost.doSetScreenState(screen_state_mask);
+ mDeviceHost.doSetScreenState(screen_state_mask, mIsWlcEnabled);
sToast_debounce = false;
@@ -1196,13 +1252,8 @@
if (mIsRecovering) {
// Intents for all users
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.addAction(Intent.ACTION_USER_PRESENT);
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_ADDED);
- mContext.registerReceiverForAllUsers(mReceiver, filter, null, null);
- mIsRecovering = false;
+ registerGlobalBroadcastsReceiver();
+ mIsRecovering = false;
}
if(mIsPowerSavingModeEnabled) {
@@ -1233,6 +1284,14 @@
WatchDogThread watchDog = new WatchDogThread("disableInternal", ROUTING_WATCHDOG_MS);
watchDog.start();
+ if (mIsWlcEnabled) {
+ if (mNfcCharging.NfcChargingOnGoing == true) {
+ mNfcCharging.disconnectNfcCharging();
+ mNfcCharging.NfcChargingOnGoing = false;
+ }
+ mNfcCharging.resetInternalValues();
+ }
+
if (mIsHceCapable) {
mCardEmulationManager.onNfcDisabled();
}
@@ -1252,7 +1311,7 @@
mReaderModeParams = null;
mDiscoveryTechParams = null;
}
- mNfcDispatcher.setForegroundDispatch(null, null, null);
+ mNfcDispatcher.resetForegroundDispatch();
boolean result;
if (!mIsAlwaysOnSupported || mIsRecovering
@@ -1418,6 +1477,12 @@
Log.d(TAG, "Disabling reader mode because app died or moved to background");
mReaderModeParams = null;
StopPresenceChecking();
+ mNfcEventLog.logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setReaderModeChange(NfcEventProto.NfcReaderModeChange.newBuilder()
+ .setFlags(0)
+ .build())
+ .build());
if (isNfcEnabled()) {
applyRouting(false);
}
@@ -1446,32 +1511,84 @@
}
}
+ public void enableNfc() {
+ saveNfcOnSetting(true);
+
+ if (shouldEnableNfc()) {
+ new EnableDisableTask().execute(TASK_ENABLE);
+ }
+ }
+
+ private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
+ ApplicationInfo applicationInfo = null;
+ try {
+ applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0, UserHandle.getUserHandleForUid(uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Failed to find app name for " + packageName);
+ return "";
+ }
+ return mContext.getPackageManager().getApplicationLabel(applicationInfo);
+ }
+
public boolean isSecureNfcEnabled() {
return mIsSecureNfcEnabled;
}
final class NfcAdapterService extends INfcAdapter.Stub {
+ private boolean isPrivileged(int callingUid) {
+ // Check for root uid to help invoking privileged APIs from rooted shell only.
+ return callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID;
+ }
+
@Override
- public boolean enable() throws RemoteException {
+ public boolean enable(String pkg) throws RemoteException {
NfcPermissions.enforceAdminPermissions(mContext);
- saveNfcOnSetting(true);
+ Log.i(TAG, "Enabling Nfc service. Package:" + pkg);
+ List<String> allowlist = new ArrayList<>(
+ Arrays.asList(mContext.getResources().getStringArray(R.array.nfc_allow_list)));
+ if (!allowlist.isEmpty() && !allowlist.contains(pkg)) {
+ Intent allowUsingNfcIntent = new Intent()
+ .putExtra(APP_NAME_ENABLING_NFC, getAppName(pkg, mUserId))
+ .setClass(mContext, NfcEnableAllowlistActivity.class);
- if (shouldEnableNfc()) {
- new EnableDisableTask().execute(TASK_ENABLE);
+ mContext.startActivityAsUser(allowUsingNfcIntent, UserHandle.CURRENT);
+ return true;
}
-
+ mNfcEventLog.logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setStateChange(NfcEventProto.NfcStateChange.newBuilder()
+ .setAppInfo(NfcEventProto.NfcAppInfo.newBuilder()
+ .setPackageName(pkg)
+ .setUid(Binder.getCallingUid())
+ .build())
+ .setEnabled(true)
+ .build())
+ .build());
+ enableNfc();
return true;
}
@Override
- public boolean disable(boolean saveState) throws RemoteException {
+ public boolean disable(boolean saveState, String pkg) throws RemoteException {
NfcPermissions.enforceAdminPermissions(mContext);
+ Log.i(TAG, "Disabling Nfc service. Package:" + pkg);
if (saveState) {
saveNfcOnSetting(false);
}
+ mNfcEventLog.logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setStateChange(NfcEventProto.NfcStateChange.newBuilder()
+ .setAppInfo(NfcEventProto.NfcAppInfo.newBuilder()
+ .setPackageName(pkg)
+ .setUid(Binder.getCallingUid())
+ .build())
+ .setEnabled(false)
+ .build())
+ .build());
new EnableDisableTask().execute(TASK_DISABLE);
return true;
@@ -1491,43 +1608,56 @@
}
@Override
+ public boolean isObserveModeEnabled() {
+ NfcPermissions.enforceUserPermissions(mContext);
+ return mDeviceHost.isObserveModeEnabled();
+ }
+
+ @Override
public boolean setObserveMode(boolean enable) {
- long token = Binder.clearCallingIdentity();
- try {
- if (!android.nfc.Flags.nfcObserveMode()) {
- return false;
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- boolean privilegedCaller = false;
int callingUid = Binder.getCallingUid();
- UserHandle user = Binder.getCallingUserHandle();
- // Allow non-foreground callers with system uid or default payment service.
- String packageName = getPackageNameFromUid(callingUid);
- if (packageName != null) {
- String defaultPaymentService = Settings.Secure.getString(
- mContext.createContextAsUser(user, 0).getContentResolver(),
- Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
- if (defaultPaymentService != null) {
- String defaultPaymentPackage =
- ComponentName.unflattenFromString(defaultPaymentService).getPackageName();
- privilegedCaller = (callingUid == Process.SYSTEM_UID
- || packageName.equals(defaultPaymentPackage));
- }
- } else {
- privilegedCaller = (callingUid == Process.SYSTEM_UID);
- }
- if (!privilegedCaller) {
+ int triggerSource =
+ NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__TRIGGER_SOURCE_UNKNOWN;
+ if (!isPrivileged(callingUid)) {
NfcPermissions.enforceUserPermissions(mContext);
- if (!mForegroundUtils.isInForeground(Binder.getCallingUid())) {
- Log.e(TAG, "setObserveMode: Caller not in foreground.");
+ String packageName = getPackageNameFromUid(callingUid);
+ if (packageName == null) {
+ Log.e(TAG, "no package name associated with non-privileged calling UID");
+ }
+ if (mCardEmulationManager.isPreferredServicePackageNameForUser(packageName,
+ UserHandle.getUserHandleForUid(callingUid).getIdentifier())) {
+ if (android.permission.flags.Flags.walletRoleEnabled()) {
+ UserHandle user = Binder.getCallingUserHandle();
+ triggerSource = packageName.equals(getWalletRoleHolder(user))
+ ? NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__WALLET_ROLE_HOLDER
+ : NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__FOREGROUND_APP;
+ } else {
+ if (mForegroundUtils.isInForeground(callingUid)) {
+ triggerSource =
+ NFC_OBSERVE_MODE_STATE_CHANGED__TRIGGER_SOURCE__FOREGROUND_APP;
+ }
+ }
+ } else {
+ Log.e(TAG, "setObserveMode: Caller not preferred NFC service.");
return false;
}
}
+
+ if (mStatsdUtils != null) {
+ mStatsdUtils.logObserveModeStateChanged(enable, triggerSource,
+ 0 /* TODO(b/334983405) measure latency */);
+ }
+
return mDeviceHost.setObserveMode(enable);
}
+ private String getWalletRoleHolder(UserHandle user) {
+ RoleManager roleManager = mContext.createContextAsUser(user, 0)
+ .getSystemService(RoleManager.class);
+ List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+ return roleHolders.isEmpty() ? null : roleHolders.get(0);
+ }
+
@Override
public void pausePolling(int timeoutInMs) {
NfcPermissions.enforceAdminPermissions(mContext);
@@ -1612,7 +1742,7 @@
}
// Short-cut the disable path
if (intent == null && filters == null && techListsParcel == null) {
- mNfcDispatcher.setForegroundDispatch(null, null, null);
+ mNfcDispatcher.resetForegroundDispatch();
return;
}
@@ -1786,16 +1916,14 @@
@Override
public void setReaderMode(IBinder binder, IAppCallback callback, int flags, Bundle extras)
throws RemoteException {
- boolean privilegedCaller = false;
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
+ boolean privilegedCaller = isPrivileged(callingUid)
+ || NfcPermissions.checkAdminPermissions(mContext);
// Allow non-foreground callers with system uid or systemui
String packageName = getPackageNameFromUid(callingUid);
if (packageName != null) {
- privilegedCaller = (callingUid == Process.SYSTEM_UID
- || packageName.equals(SYSTEM_UI));
- } else {
- privilegedCaller = (callingUid == Process.SYSTEM_UID);
+ privilegedCaller |= packageName.equals(SYSTEM_UI);
}
Log.d(TAG, "setReaderMode: uid=" + callingUid + ", packageName: "
+ packageName + ", flags: " + flags);
@@ -1863,6 +1991,16 @@
Log.e(TAG, "Reader mode Binder was never registered.");
}
}
+ mNfcEventLog.logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setReaderModeChange(NfcEventProto.NfcReaderModeChange.newBuilder()
+ .setAppInfo(NfcEventProto.NfcAppInfo.newBuilder()
+ .setPackageName(packageName)
+ .setUid(callingUid)
+ .build())
+ .setFlags(flags)
+ .build())
+ .build());
if (isNfcEnabled()) {
applyRouting(false);
}
@@ -1907,13 +2045,7 @@
@Override
public boolean deviceSupportsNfcSecure() {
- String skuList[] = mContext.getResources().getStringArray(
- R.array.config_skuSupportsSecureNfc);
- String sku = SystemProperties.get("ro.boot.hardware.sku");
- if (TextUtils.isEmpty(sku) || !Utils.arrayContains(skuList, sku)) {
- return false;
- }
- return true;
+ return mIsSecureNfcCapable;
}
@Override
@@ -1941,6 +2073,48 @@
availableNfcAntennas);
}
+ @Override
+ public boolean setWlcEnabled(boolean enable) {
+ if (!mIsWlcCapable) {
+ return false;
+ }
+ NfcPermissions.enforceAdminPermissions(mContext);
+ // enable or disable WLC
+ if (DBG) Log.d(TAG, "setWlcEnabled: " + enable);
+ synchronized (NfcService.this) {
+ // check whether NFC is enabled
+ if (!isNfcEnabled()) {
+ return false;
+ }
+ mPrefsEditor.putBoolean(PREF_NFC_CHARGING_ON, enable);
+ mPrefsEditor.apply();
+ mIsWlcEnabled = enable;
+ mBackupManager.dataChanged();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isWlcEnabled() throws RemoteException {
+ if (!mIsWlcCapable) {
+ return false;
+ }
+ // check whether WLC is enabled or disabled
+ synchronized (NfcService.this) {
+ return mIsWlcEnabled;
+ }
+ }
+
+ @Override
+ public WlcListenerDeviceInfo getWlcListenerDeviceInfo() {
+ if (!mIsWlcCapable) {
+ return null;
+ }
+ synchronized (NfcService.this) {
+ return mWlcListenerDeviceInfo;
+ }
+ }
+
private int computeLockscreenPollMask(int[] techList) {
Map<Integer, Integer> techCodeToMask = new HashMap<Integer, Integer>();
@@ -2111,6 +2285,97 @@
return mIsReaderOptionEnabled;
}
+ @Override
+ public void registerWlcStateListener(
+ INfcWlcStateListener listener) throws RemoteException {
+ if (!mIsWlcCapable) {
+ return;
+ }
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ mWlcStateListener.add(listener);
+ }
+
+ @Override
+ public void unregisterWlcStateListener(
+ INfcWlcStateListener listener) throws RemoteException {
+ if (!mIsWlcCapable) {
+ return;
+ }
+ NfcPermissions.enforceAdminPermissions(mContext);
+
+ mWlcStateListener.remove(listener);
+ }
+
+ @Override
+ public void notifyPollingLoop(PollingFrame frame) {
+ try {
+ byte[] data;
+ int type = frame.getType();
+ int gain = frame.getVendorSpecificGain();
+ byte[] frame_data = frame.getData();
+
+ long timestamp = frame.getTimestamp();
+ HexFormat format = HexFormat.ofDelimiter(" ");
+ String timestampBytes = format.formatHex(new byte[] {
+ (byte) (timestamp >>> 24),
+ (byte) (timestamp >>> 16),
+ (byte) (timestamp >>> 8),
+ (byte) timestamp });
+ int frame_data_length = frame_data == null ? 0 : frame_data.length;
+ String frame_data_str = frame_data_length == 0 ? "" : " " + format.formatHex(frame_data);
+ String type_str = "FF";
+ switch (type) {
+ case PollingFrame.POLLING_LOOP_TYPE_ON:
+ type_str = "00";
+ data = new byte[] { 0x01 };
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_OFF:
+ type_str = "00";
+ data = new byte[] { 0x00 };
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_A:
+ type_str = "01";
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_B:
+ type_str = "02";
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_F:
+ type_str = "03";
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_UNKNOWN:
+ type_str = "07";
+ break;
+ }
+ data = format.parseHex("6f 0C " + String.format("%02x", 9 + frame_data_length)
+ + " 03 " + type_str
+ + " 00 " + String.format("%02x", 5 + frame_data_length) + " "
+ + timestampBytes + " " + String.format("%02x", gain) + frame_data_str);
+ ((NativeNfcManager) mDeviceHost).notifyPollingLoopFrame(data.length, data);
+ } catch (Exception ex) {
+ Log.e(TAG, "error when notifying polling loop", ex);
+ }
+ }
+
+ @Override
+ public void notifyHceDeactivated() {
+ try {
+ mCardEmulationManager.onHostCardEmulationDeactivated(1);
+ } catch (Exception ex) {
+ Log.e(TAG, "error when notifying HCE deactivated", ex);
+ }
+ }
+
+ @Override
+ public int handleShellCommand(@NonNull ParcelFileDescriptor in,
+ @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
+ @NonNull String[] args) {
+
+ NfcShellCommand shellCommand = new NfcShellCommand(NfcService.this, mContext);
+ return shellCommand.exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
+ err.getFileDescriptor(), args);
+ }
+
private static boolean isPowerSavingModeCmd(int gid, int oid, byte[] payload) {
return gid == NCI_GID_PROP && oid == NCI_MSG_PROP_ANDROID && payload.length > 0
&& payload[0] == NCI_MSG_PROP_ANDROID_POWER_SAVING;
@@ -2134,7 +2399,8 @@
NfcVendorNciResponse response =
mDeviceHost.sendRawVendorCmd(mt, gid, oid, payload);
if (response.status == NCI_STATUS_OK) {
- sendVendorNciResponse(response.gid, response.oid, response.payload);
+ mHandler.post(() -> mNfcAdapter.sendVendorNciResponse(
+ response.gid, response.oid, response.payload));
}
return Integer.valueOf(response.status);
}
@@ -2154,19 +2420,21 @@
}
@Override
- public void registerVendorExtensionCallback(INfcVendorNciCallback callbacks)
+ public synchronized void registerVendorExtensionCallback(INfcVendorNciCallback callbacks)
throws RemoteException {
if (DBG) Log.i(TAG, "Register the callback");
NfcPermissions.enforceAdminPermissions(mContext);
mNfcVendorNciCallBack = callbacks;
+ mDeviceHost.enableVendorNciNotifications(true);
}
@Override
- public void unregisterVendorExtensionCallback(INfcVendorNciCallback callbacks)
+ public synchronized void unregisterVendorExtensionCallback(INfcVendorNciCallback callbacks)
throws RemoteException {
if (DBG) Log.i(TAG, "Unregister the callback");
NfcPermissions.enforceAdminPermissions(mContext);
mNfcVendorNciCallBack = null;
+ mDeviceHost.enableVendorNciNotifications(false);
}
@Override
@@ -2193,7 +2461,7 @@
}
private synchronized void sendVendorNciResponse(int gid, int oid, byte[] payload) {
- if (DBG) Log.i(TAG, "onVendorNciResponseReceived");
+ if (VDBG) Log.i(TAG, "onVendorNciResponseReceived");
if (mNfcVendorNciCallBack != null) {
try {
mNfcVendorNciCallBack.onVendorResponseReceived(gid, oid, payload);
@@ -2204,7 +2472,7 @@
}
private synchronized void sendVendorNciNotification(int gid, int oid, byte[] payload) {
- if (DBG) Log.i(TAG, "sendVendorNciNotification");
+ if (VDBG) Log.i(TAG, "sendVendorNciNotification");
if (mNfcVendorNciCallBack != null) {
try {
mNfcVendorNciCallBack.onVendorNotificationReceived(gid, oid, payload);
@@ -2215,16 +2483,6 @@
}
}
- private void sendVendorNciNotification(int gid, int oid, byte[] payload) {
- if (DBG) Log.i(TAG, "sendVendorNciNotification");
- if (mNfcVendorNciCallBack != null) {
- try {
- mNfcVendorNciCallBack.onVendorNotificationReceived(gid, oid, payload);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to send vendor notification", e);
- }
- }
- }
final class SeServiceDeathRecipient implements IBinder.DeathRecipient {
@Override
@@ -2794,10 +3052,48 @@
new KeyguardLockedStateListener() {
@Override
public void onKeyguardLockedStateChanged(boolean isKeyguardLocked) {
- applyScreenState(mScreenStateHelper.checkScreenState());
+ if (!mIsWlcCapable || !mNfcCharging.NfcChargingOnGoing) {
+ applyScreenState(mScreenStateHelper.checkScreenState());
+ }
}
};
+ private void addThermalStatusListener() {
+ try {
+ if (mPowerManager != null) {
+ mPowerManager.addThermalStatusListener(mContext.getMainExecutor(),
+ mOnThermalStatusChangedListener);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in addThermalStatusListener " + e);
+ }
+ }
+
+ /**
+ * Receives Thermal state updates
+ */
+ private OnThermalStatusChangedListener mOnThermalStatusChangedListener =
+ new OnThermalStatusChangedListener() {
+ @Override
+ public void onThermalStatusChanged(int status) {
+ switch (status) {
+ case PowerManager.THERMAL_STATUS_MODERATE:
+ Log.d(TAG, "Thermal status changed to MODERATE");
+ break;
+ case PowerManager.THERMAL_STATUS_SEVERE:
+ Log.d(TAG, "Thermal status changed to SEVERE");
+ break;
+ case PowerManager.THERMAL_STATUS_CRITICAL:
+ Log.d(TAG, "Thermal status changed to CRITICAL");
+ break;
+ default:
+ Log.d(TAG, "Unknown thermal status: " + status);
+ break;
+ }
+ }
+ };
+
+
/**
* Read mScreenState and apply NFC-C polling and NFC-EE routing
*/
@@ -3042,6 +3338,21 @@
mHandler.sendEmptyMessage(MSG_COMMIT_ROUTING);
}
+ public boolean sendScreenMessageAfterNfcCharging() {
+ if (DBG) Log.d(TAG, "sendScreenMessageAfterNfcCharging() ");
+
+ if (mPendingPowerStateUpdate == true) {
+ int screenState = mScreenStateHelper.checkScreenState();
+ if (DBG) Log.d(TAG,
+ "sendScreenMessageAfterNfcCharging - applying postponed screen state "
+ + screenState);
+ NfcService.getInstance().sendMessage(NfcService.MSG_APPLY_SCREEN_STATE, screenState);
+ mPendingPowerStateUpdate = false;
+ return true;
+ }
+ return false;
+ }
+
public boolean sendData(byte[] data) {
return mDeviceHost.sendRawFrame(data);
}
@@ -3086,6 +3397,10 @@
}
final class NfcServiceHandler extends Handler {
+ public NfcServiceHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -3269,11 +3584,36 @@
mLastReadNdefMessage = ndefMsg;
- tag.startPresenceChecking(presenceCheckDelay, callback);
- dispatchTagEndpoint(tag, readerParams);
+ if (mIsWlcEnabled) {
+ if (DBG) Log.d(TAG, "Wlc enabled, check for WLC_CAP record");
+
+ if (!mNfcCharging.NfcChargingMode
+ && (mNfcCharging.checkWlcCapMsg(ndefMsg) == true)) {
+ if (DBG) Log.d(TAG, "checkWlcCapMsg returned true");
+ if (mNfcCharging.startNfcCharging(tag)) {
+ mNfcCharging.NfcChargingMode = true;
+ if (DBG) Log.d(TAG, "Nfc charging mode started successfully");
+ } else {
+ if (DBG) Log.d(TAG, "Nfc charging mode not detected");
+ }
+ } else if (mIsRWCapable) {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ } else {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ }
+ } else if (mIsRWCapable) {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ dispatchTagEndpoint(tag, readerParams);
+ } else {
+ tag.startPresenceChecking(presenceCheckDelay, callback);
+ }
break;
case MSG_RF_FIELD_ACTIVATED:
+ if (mCardEmulationManager != null) {
+ mCardEmulationManager.onFieldChangeDetected(true);
+ }
Intent fieldOnIntent = new Intent(ACTION_RF_FIELD_ON_DETECTED);
sendNfcPermissionProtectedBroadcast(fieldOnIntent);
if (mIsSecureNfcEnabled) {
@@ -3281,6 +3621,9 @@
}
break;
case MSG_RF_FIELD_DEACTIVATED:
+ if (mCardEmulationManager != null) {
+ mCardEmulationManager.onFieldChangeDetected(false);
+ }
Intent fieldOffIntent = new Intent(ACTION_RF_FIELD_OFF_DETECTED);
sendNfcPermissionProtectedBroadcast(fieldOffIntent);
break;
@@ -3332,7 +3675,7 @@
applyRouting(false);
}
- mDeviceHost.doSetScreenState(screen_state_mask);
+ mDeviceHost.doSetScreenState(screen_state_mask, mIsWlcEnabled);
} finally {
mRoutingWakeLock.release();
}
@@ -3724,7 +4067,7 @@
}
}
- private NfcServiceHandler mHandler = new NfcServiceHandler();
+ private NfcServiceHandler mHandler;
class ApplyRoutingTask extends AsyncTask<Integer, Void, Void> {
@Override
@@ -3756,6 +4099,13 @@
|| action.equals(Intent.ACTION_SCREEN_OFF)
|| action.equals(Intent.ACTION_USER_PRESENT)) {
// Perform applyRouting() in AsyncTask to serialize blocking calls
+
+ if (mIsWlcCapable && mNfcCharging.NfcChargingOnGoing) {
+ Log.d(TAG,
+ "MSG_APPLY_SCREEN_STATE postponing due to a charging pier device");
+ mPendingPowerStateUpdate = true;
+ return;
+ }
if (action.equals(Intent.ACTION_SCREEN_ON)) {
synchronized (NfcService.this) {
mPollDelayCount = 0;
@@ -3787,6 +4137,20 @@
UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0))
.startNotification();
}
+ } else if (action.equals(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED) ||
+ action.equals(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)) {
+ if (!mContext.getResources().getBoolean(R.bool.restart_on_sim_change)) return;
+ int state = intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
+ TelephonyManager.SIM_STATE_UNKNOWN);
+ // Use |SIM_STATE_ABSENT| for detecting sim removal
+ // Use |SIM_STATE_LOADED| for detecting sim insertion and ready.
+ if (state == TelephonyManager.SIM_STATE_ABSENT
+ || state == TelephonyManager.SIM_STATE_LOADED) {
+ if (isNfcEnabled()) {
+ Log.w(TAG, "Restarting NFC stack on SIM state change, SIM_STATE: " + state);
+ restartStack();
+ }
+ }
}
}
};
@@ -3853,16 +4217,24 @@
screenState = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;
}
}
+ if (DBG) Log.d(TAG, "applyScreenState(): screenState=" + screenState );
if (mScreenState != screenState) {
if (nci_version != NCI_VERSION_2_0) {
new ApplyRoutingTask().execute(Integer.valueOf(screenState));
}
+ if (DBG) Log.d(TAG, "applyScreenState(): screenState != mScreenState=" + mScreenState );
sendMessage(NfcService.MSG_APPLY_SCREEN_STATE, screenState);
}
}
private void setPaymentForegroundPreference(int user) {
- Context uc = mContext.createContextAsUser(UserHandle.of(user), 0);
+ Context uc;
+ try {
+ uc = mContext.createContextAsUser(UserHandle.of(user), 0);
+ } catch (IllegalStateException e) {
+ Log.d(TAG, "Fail to get user context for user: " + user);
+ return;
+ }
try {
// Check whether the Settings.Secure.NFC_PAYMENT_FOREGROUND exists or not.
Settings.Secure.getInt(uc.getContentResolver(),
@@ -4015,6 +4387,9 @@
pw.println("mIsSecureNfcEnabled=" + mIsSecureNfcEnabled);
pw.println("mIsReaderOptionEnabled=" + mIsReaderOptionEnabled);
pw.println("mIsAlwaysOnSupported=" + mIsAlwaysOnSupported);
+ if(mIsWlcCapable) {
+ pw.println("WlcEnabled=" + mIsWlcEnabled);
+ }
pw.println("SnoopLogMode=" + NFC_SNOOP_LOG_MODE);
pw.println("VendorDebugEnabled=" + NFC_VENDOR_DEBUG_ENABLED);
pw.println("mIsPowerSavingModeEnabled=" + mIsPowerSavingModeEnabled);
@@ -4027,6 +4402,7 @@
mRoutingTableParser.dump(mDeviceHost, pw);
}
dumpTagAppPreference(pw);
+ mNfcInjector.getNfcEventLog().dump(fd, pw, args);
copyNativeCrashLogsIfAny(pw);
pw.flush();
mDeviceHost.dump(fd);
diff --git a/src/com/android/nfc/NfcShellCommand.java b/src/com/android/nfc/NfcShellCommand.java
new file mode 100644
index 0000000..36ec00e
--- /dev/null
+++ b/src/com/android/nfc/NfcShellCommand.java
@@ -0,0 +1,172 @@
+/*
+ * 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 com.android.nfc;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.text.TextUtils;
+
+import com.android.modules.utils.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+
+/**
+ * Interprets and executes 'adb shell cmd nfc [args]'.
+ *
+ * To add new commands:
+ * - onCommand: Add a case "<command>" execute. Return a 0
+ * if command executed successfully.
+ * - onHelp: add a description string.
+ *
+ * Permissions: currently root permission is required for some commands. Others will
+ * enforce the corresponding API permissions.
+ */
+public class NfcShellCommand extends BasicShellCommandHandler {
+ private static final int DISABLE_POLLING_FLAGS = 0x1000;
+ private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+ // These don't require root access. However, these do perform permission checks in the
+ // corresponding binder methods in mNfcService.mNfcAdapter.
+ // Note: Any time you invoke a method from an internal class, consider making it privileged
+ // since these shell commands are available on production builds, we don't want apps to use
+ // this command to bypass security restrictions. mNfcService.mNfcAdapter binder
+ // methods already enforce permissions of the invoking shell (non-rooted shell has limited
+ // set of privileges).
+ private static final String[] NON_PRIVILEGED_COMMANDS = {
+ "help",
+ "disable-nfc",
+ "enable-nfc",
+ "status",
+ };
+ private final NfcService mNfcService;
+ private final Context mContext;
+
+ NfcShellCommand(NfcService nfcService, Context context) {
+ mNfcService = nfcService;
+ mContext = context;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ // Treat no command as help command.
+ if (cmd == null || cmd.equals("")) {
+ cmd = "help";
+ }
+ // Explicit exclusion from root permission
+ if (ArrayUtils.indexOf(NON_PRIVILEGED_COMMANDS, cmd) == -1) {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.ROOT_UID) {
+ throw new SecurityException(
+ "Uid " + uid + " does not have access to " + cmd + " nfc command "
+ + "(or such command doesn't exist)");
+ }
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch (cmd) {
+ case "status":
+ printStatus(pw);
+ return 0;
+ case "disable-nfc":
+ String stringSaveState = getNextArg();
+ boolean saveState = false;
+ if (TextUtils.equals(stringSaveState, "[persist]")) {
+ saveState = true;
+ }
+ mNfcService.mNfcAdapter.disable(saveState, mContext.getPackageName());
+ return 0;
+ case "enable-nfc":
+ mNfcService.mNfcAdapter.enable(mContext.getPackageName());
+ return 0;
+ case "set-reader-mode":
+ boolean enable_polling =
+ getNextArgRequiredTrueOrFalse("enable-polling", "disable-polling");
+ int flags = enable_polling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ mNfcService.mNfcAdapter.setReaderMode(new Binder(), null, flags, null);
+ return 0;
+ case "set-observe-mode":
+ boolean enable = getNextArgRequiredTrueOrFalse("enable", "disable");
+ mNfcService.mNfcAdapter.setObserveMode(enable);
+ return 0;
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (IllegalArgumentException e) {
+ pw.println("Invalid args for " + cmd + ": ");
+ e.printStackTrace(pw);
+ return -1;
+ } catch (Exception e) {
+ pw.println("Exception while executing nfc shell command" + cmd + ": ");
+ e.printStackTrace(pw);
+ return -1;
+ }
+ }
+
+ private static boolean argTrueOrFalse(String arg, String trueString, String falseString) {
+ if (trueString.equals(arg)) {
+ return true;
+ } else if (falseString.equals(arg)) {
+ return false;
+ } else {
+ throw new IllegalArgumentException("Expected '" + trueString + "' or '" + falseString
+ + "' as next arg but got '" + arg + "'");
+ }
+
+ }
+
+ private boolean getNextArgRequiredTrueOrFalse(String trueString, String falseString)
+ throws IllegalArgumentException {
+ String nextArg = getNextArgRequired();
+ return argTrueOrFalse(nextArg, trueString, falseString);
+ }
+
+ private void printStatus(PrintWriter pw) throws RemoteException {
+ pw.println("Nfc is " + (mNfcService.isNfcEnabled() ? "enabled" : "disabled"));
+ }
+
+ private void onHelpNonPrivileged(PrintWriter pw) {
+ pw.println(" status");
+ pw.println(" Gets status of UWB stack");
+ pw.println(" enable-nfc");
+ pw.println(" Toggle NFC on");
+ pw.println(" disable-nfc [persist]");
+ pw.println(" Toggle NFC off (optionally make it persistent)");
+ }
+
+ private void onHelpPrivileged(PrintWriter pw) {
+ pw.println(" set-observe-mode enable|disable");
+ pw.println(" Enable or disable observe mode.");
+ pw.println(" set-reader-mode enable-polling|disable-polling");
+ pw.println(" Enable or reader mode polling");
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("NFC (Near-field communication) commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ onHelpNonPrivileged(pw);
+ if (Binder.getCallingUid() == Process.ROOT_UID) {
+ onHelpPrivileged(pw);
+ }
+ pw.println();
+ }
+}
diff --git a/src/com/android/nfc/RegisteredComponentCache.java b/src/com/android/nfc/RegisteredComponentCache.java
index 0d703c0..34c450b 100644
--- a/src/com/android/nfc/RegisteredComponentCache.java
+++ b/src/com/android/nfc/RegisteredComponentCache.java
@@ -37,7 +37,9 @@
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -46,7 +48,8 @@
public class RegisteredComponentCache {
private static final String TAG = "RegisteredComponentCache";
private static final boolean DEBUG =
- NfcProperties.debug_enabled().orElse(false);
+ NfcProperties.debug_enabled().orElse(true);
+ private static final boolean VDBG = false; // turn on for local testing.
final Context mContext;
final String mAction;
@@ -54,7 +57,7 @@
final AtomicReference<BroadcastReceiver> mReceiver;
// synchronized on this
- private ArrayList<ComponentInfo> mComponents;
+ private ArrayList<ComponentInfo> mComponents = new ArrayList<>();
public RegisteredComponentCache(Context context, String action, String metaDataName) {
mContext = context;
@@ -107,6 +110,21 @@
}
return out.toString();
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ComponentInfo) {
+ ComponentInfo oCI = (ComponentInfo) other;
+ return Objects.equals(resolveInfo.activityInfo, oCI.resolveInfo.activityInfo)
+ && Arrays.equals(techs, oCI.techs);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(Arrays.hashCode(techs), resolveInfo.activityInfo);
+ }
}
/**
@@ -170,8 +188,19 @@
}
}
- if (DEBUG) {
+ if (VDBG) {
+ Log.i(TAG, "Components => ");
dump(components);
+ } else {
+ // dump only new components added or removed
+ ArrayList<ComponentInfo> newComponents = new ArrayList<>(components);
+ newComponents.removeAll(mComponents);
+ ArrayList<ComponentInfo> removedComponents = new ArrayList<>(mComponents);
+ removedComponents.removeAll(components);
+ Log.i(TAG, "New Components => ");
+ dump(newComponents);
+ Log.i(TAG, "Removed Components => ");
+ dump(removedComponents);
}
synchronized (this) {
diff --git a/src/com/android/nfc/RoutingTableParser.java b/src/com/android/nfc/RoutingTableParser.java
index 775e65f..56ee5dc 100644
--- a/src/com/android/nfc/RoutingTableParser.java
+++ b/src/com/android/nfc/RoutingTableParser.java
@@ -30,7 +30,7 @@
* Parse the Routing Table from the last backup lmrt cmd and dump it with a clear typography
*/
public class RoutingTableParser {
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
private static final String TAG = "RoutingTableParser";
private static int sRoutingTableSize = 0;
private static int sRoutingTableMaxSize = 0;
diff --git a/src/com/android/nfc/cardemulation/AidRoutingManager.java b/src/com/android/nfc/cardemulation/AidRoutingManager.java
index 11babac..fc9fc34 100644
--- a/src/com/android/nfc/cardemulation/AidRoutingManager.java
+++ b/src/com/android/nfc/cardemulation/AidRoutingManager.java
@@ -38,7 +38,7 @@
static final String TAG = "AidRoutingManager";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final int ROUTE_HOST = 0x00;
diff --git a/src/com/android/nfc/cardemulation/CardEmulationManager.java b/src/com/android/nfc/cardemulation/CardEmulationManager.java
index 4d0919d..506e779 100644
--- a/src/com/android/nfc/cardemulation/CardEmulationManager.java
+++ b/src/com/android/nfc/cardemulation/CardEmulationManager.java
@@ -15,7 +15,10 @@
*/
package com.android.nfc.cardemulation;
+import android.annotation.TargetApi;
+import android.annotation.FlaggedApi;
import android.app.ActivityManager;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -25,11 +28,11 @@
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
import android.nfc.NfcAdapter;
-import android.nfc.NfcManager;
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.NfcFServiceInfo;
+import android.nfc.cardemulation.PollingFrame;
import android.os.Binder;
import android.os.Bundle;
import android.os.Looper;
@@ -44,6 +47,7 @@
import android.util.Log;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.nfc.ForegroundUtils;
import com.android.nfc.NfcPermissions;
import com.android.nfc.NfcService;
@@ -54,6 +58,7 @@
import java.util.List;
import com.android.nfc.R;
+import android.permission.flags.Flags;
/**
* CardEmulationManager is the central entity
@@ -72,9 +77,9 @@
*/
public class CardEmulationManager implements RegisteredServicesCache.Callback,
RegisteredNfcFServicesCache.Callback, PreferredServices.Callback,
- EnabledNfcFServices.Callback {
+ EnabledNfcFServices.Callback, WalletRoleObserver.Callback {
static final String TAG = "CardEmulationManager";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final int NFC_HCE_APDU = 0x01;
static final int NFC_HCE_NFCF = 0x04;
@@ -100,14 +105,16 @@
final HostEmulationManager mHostEmulationManager;
final HostNfcFEmulationManager mHostNfcFEmulationManager;
final PreferredServices mPreferredServices;
+
+ final WalletRoleObserver mWalletRoleObserver;
final EnabledNfcFServices mEnabledNfcFServices;
final Context mContext;
final CardEmulationInterface mCardEmulationInterface;
final NfcFCardEmulationInterface mNfcFCardEmulationInterface;
final PowerManager mPowerManager;
boolean mNotSkipAid;
-
- final ForegroundUtils mForegroundUtils;
+
+ final ForegroundUtils mForegroundUtils;
private int mForegroundUid;
RoutingOptionManager mRoutingOptionManager;
@@ -118,25 +125,71 @@
mContext = context;
mCardEmulationInterface = new CardEmulationInterface();
mNfcFCardEmulationInterface = new NfcFCardEmulationInterface();
- mForegroundUtils = ForegroundUtils.getInstance(context.getSystemService(ActivityManager.class));
- mAidCache = new RegisteredAidCache(context);
+ mForegroundUtils = ForegroundUtils.getInstance(
+ context.getSystemService(ActivityManager.class));
+ mWalletRoleObserver = new WalletRoleObserver(context,
+ context.getSystemService(RoleManager.class), this);
+ mAidCache = new RegisteredAidCache(context, mWalletRoleObserver);
mT3tIdentifiersCache = new RegisteredT3tIdentifiersCache(context);
mHostEmulationManager =
new HostEmulationManager(context, Looper.getMainLooper(), mAidCache);
mHostNfcFEmulationManager = new HostNfcFEmulationManager(context, mT3tIdentifiersCache);
mServiceCache = new RegisteredServicesCache(context, this);
mNfcFServicesCache = new RegisteredNfcFServicesCache(context, this);
- mPreferredServices = new PreferredServices(context, mServiceCache, mAidCache, this);
+ mPreferredServices = new PreferredServices(context, mServiceCache, mAidCache,
+ mWalletRoleObserver, this);
mEnabledNfcFServices = new EnabledNfcFServices(
context, mNfcFServicesCache, mT3tIdentifiersCache, this);
- mServiceCache.initialize();
- mNfcFServicesCache.initialize();
mPowerManager = context.getSystemService(PowerManager.class);
mRoutingOptionManager = RoutingOptionManager.getInstance();
mOffHostRouteEse = mRoutingOptionManager.getOffHostRouteEse();
mOffHostRouteUicc = mRoutingOptionManager.getOffHostRouteUicc();
+ initialize();
+ }
+ @VisibleForTesting
+ CardEmulationManager(Context context,
+ ForegroundUtils foregroundUtils,
+ WalletRoleObserver walletRoleObserver,
+ RegisteredAidCache registeredAidCache,
+ RegisteredT3tIdentifiersCache registeredT3tIdentifiersCache,
+ HostEmulationManager hostEmulationManager,
+ HostNfcFEmulationManager hostNfcFEmulationManager,
+ RegisteredServicesCache registeredServicesCache,
+ RegisteredNfcFServicesCache registeredNfcFServicesCache,
+ PreferredServices preferredServices,
+ EnabledNfcFServices enabledNfcFServices,
+ RoutingOptionManager routingOptionManager,
+ PowerManager powerManager) {
+ mContext = context;
+ mCardEmulationInterface = new CardEmulationInterface();
+ mNfcFCardEmulationInterface = new NfcFCardEmulationInterface();
+ mForegroundUtils = foregroundUtils;
+ mWalletRoleObserver = walletRoleObserver;
+ mAidCache = registeredAidCache;
+ mT3tIdentifiersCache = registeredT3tIdentifiersCache;
+ mHostEmulationManager = hostEmulationManager;
+ mHostNfcFEmulationManager = hostNfcFEmulationManager;
+ mServiceCache = registeredServicesCache;
+ mNfcFServicesCache = registeredNfcFServicesCache;
+ mPreferredServices = preferredServices;
+ mEnabledNfcFServices = enabledNfcFServices;
+ mPowerManager = powerManager;
+ mRoutingOptionManager = routingOptionManager;
+ mOffHostRouteEse = mRoutingOptionManager.getOffHostRouteEse();
+ mOffHostRouteUicc = mRoutingOptionManager.getOffHostRouteUicc();
+ initialize();
+ }
+
+ private void initialize() {
+ mServiceCache.initialize();
+ mNfcFServicesCache.initialize();
mForegroundUid = Process.INVALID_UID;
+ if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ int currentUser = ActivityManager.getCurrentUser();
+ onWalletRoleHolderChanged(
+ mWalletRoleObserver.getDefaultWalletRoleHolder(currentUser), currentUser);
+ }
}
public INfcCardEmulation getNfcCardEmulationInterface() {
@@ -147,8 +200,13 @@
return mNfcFCardEmulationInterface;
}
- public void onPollingLoopDetected(Bundle pollingFrame) {
- mHostEmulationManager.onPollingLoopDetected(pollingFrame);
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void onPollingLoopDetected(List<PollingFrame> pollingFrames) {
+ mHostEmulationManager.onPollingLoopDetected(pollingFrames);
+ }
+
+ public void onFieldChangeDetected(boolean fieldOn) {
+ mHostEmulationManager.onFieldChangeDetected(fieldOn);
}
public void onHostCardEmulationActivated(int technology) {
@@ -200,6 +258,7 @@
}
public void onUserSwitched(int userId) {
+ mWalletRoleObserver.onUserSwitched(userId);
// for HCE
mServiceCache.onUserSwitched();
mPreferredServices.onUserSwitched(userId);
@@ -296,13 +355,17 @@
@Override
public void onServicesUpdated(int userId, List<ApduServiceInfo> services,
boolean validateInstalled) {
- // Verify defaults are still the same
- verifyDefaults(userId, services, validateInstalled);
+ if (!mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ // Verify defaults are still the same
+ verifyDefaults(userId, services, validateInstalled);
+ }
// Update the AID cache
mAidCache.onServicesUpdated(userId, services);
// Update the preferred services list
mPreferredServices.onServicesUpdated();
-
+ if (android.nfc.Flags.nfcReadPollingLoop()) {
+ mHostEmulationManager.updatePollingLoopFilters(userId, services);
+ }
NfcService.getInstance().onPreferredPaymentChanged(NfcAdapter.PREFERRED_PAYMENT_UPDATED);
}
@@ -524,6 +587,10 @@
if (!isServiceRegistered(userId, service)) {
return false;
}
+ if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ return service.getPackageName()
+ .equals(mWalletRoleObserver.getDefaultWalletRoleHolder(userId));
+ }
ComponentName defaultService =
getDefaultServiceForCategory(userId, category, true);
return (defaultService != null && defaultService.equals(service));
@@ -563,13 +630,14 @@
}
@Override
- public boolean setServiceObserveModeDefault(int userId,
+ public boolean setShouldDefaultToObserveModeForService(int userId,
ComponentName service, boolean enable) {
NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered(userId, service)) {
return false;
}
- return mServiceCache.setServiceObserveModeDefault(userId, Binder.getCallingUid(),
+ return mServiceCache.setShouldDefaultToObserveModeForService(userId, Binder.getCallingUid(),
service, enable);
}
@@ -579,6 +647,7 @@
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
if (!isServiceRegistered(userId, service)) {
+ Log.e(TAG, "service ("+ service + ") isn't registered for user " + userId);
return false;
}
if (!mServiceCache.registerAidGroupForService(userId, Binder.getCallingUid(), service,
@@ -591,6 +660,62 @@
}
@Override
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopFilterForService(int userId, ComponentName service,
+ String pollingLoopFilter, boolean autoTransact) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ Log.e(TAG, "service ("+ service + ") isn't registered for user " + userId);
+ return false;
+ }
+ return mServiceCache.registerPollingLoopFilterForService(userId, Binder.getCallingUid(),
+ service, pollingLoopFilter, autoTransact);
+ }
+
+ @Override
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean removePollingLoopFilterForService(int userId, ComponentName service,
+ String pollingLoopFilter) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ Log.e(TAG, "service ("+ service + ") isn't registered for user " + userId);
+ return false;
+ }
+ return mServiceCache.removePollingLoopFilterForService(userId, Binder.getCallingUid(),
+ service, pollingLoopFilter);
+ }
+
+ @Override
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopPatternFilterForService(int userId, ComponentName service,
+ String pollingLoopPatternFilter, boolean autoTransact) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ Log.e(TAG, "service ("+ service + ") isn't registed for user " + userId);
+ return false;
+ }
+ return mServiceCache.registerPollingLoopPatternFilterForService(userId,
+ Binder.getCallingUid(), service, pollingLoopPatternFilter, autoTransact);
+ }
+
+ @Override
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean removePollingLoopPatternFilterForService(int userId, ComponentName service,
+ String pollingLoopPatternFilter) throws RemoteException {
+ NfcPermissions.validateUserId(userId);
+ NfcPermissions.enforceUserPermissions(mContext);
+ if (!isServiceRegistered(userId, service)) {
+ Log.e(TAG, "service ("+ service + ") isn't registed for user " + userId);
+ return false;
+ }
+ return mServiceCache.removePollingLoopPatternFilterForService(userId,
+ Binder.getCallingUid(), service, pollingLoopPatternFilter);
+ }
+
+ @Override
public boolean setOffHostForService(int userId, ComponentName service, String offHostSE) {
NfcPermissions.validateUserId(userId);
NfcPermissions.enforceUserPermissions(mContext);
@@ -703,6 +828,11 @@
@Override
public boolean isDefaultPaymentRegistered() throws RemoteException {
+ if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ int callingUserId = Binder.getCallingUserHandle().getIdentifier();
+ return mWalletRoleObserver
+ .getDefaultWalletRoleHolder(callingUserId) != null;
+ }
String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
return defaultComponent != null ? true : false;
@@ -714,7 +844,8 @@
", technology " + technology);
int callingUid = Binder.getCallingUid();
- if (!mForegroundUtils.registerUidToBackgroundCallback(mForegroundCallback, callingUid)) {
+ if (!mForegroundUtils
+ .registerUidToBackgroundCallback(mForegroundCallback, callingUid)) {
Log.e(TAG, "overrideRoutingTable: Caller is not in foreground.");
return false;
}
@@ -722,7 +853,8 @@
int protocolRoute = getRouteForSecureElement(protocol);
int technologyRoute = getRouteForSecureElement(technology);
- if (DBG) Log.d(TAG, "protocolRoute " + protocolRoute + ", technologyRoute " + technologyRoute);
+ if (DBG) Log.d(TAG, "protocolRoute " + protocolRoute +
+ ", technologyRoute " + technologyRoute);
// mRoutingOptionManager.overrideDefaultRoute(protocolRoute);
mRoutingOptionManager.overrideDefaultIsoDepRoute(protocolRoute);
@@ -902,39 +1034,52 @@
@Override
public void onPreferredPaymentServiceChanged(int userId, ComponentName service) {
+ Log.i(TAG, "onPreferredPaymentServiceChanged");
mAidCache.onPreferredPaymentServiceChanged(userId, service);
mHostEmulationManager.onPreferredPaymentServiceChanged(userId, service);
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_CHANGED);
+ updateForShouldDefaultToObserveMode(userId);
}
@Override
public void onPreferredForegroundServiceChanged(int userId, ComponentName service) {
+ Log.i(TAG, "onPreferredForegroundServiceChanged");
mAidCache.onPreferredForegroundServiceChanged(userId, service);
mHostEmulationManager.onPreferredForegroundServiceChanged(userId, service);
NfcService.getInstance().onPreferredPaymentChanged(
NfcAdapter.PREFERRED_PAYMENT_CHANGED);
+ updateForShouldDefaultToObserveMode(userId);
+ }
+
+ private void updateForShouldDefaultToObserveMode(int userId) {
long token = Binder.clearCallingIdentity();
try {
if (!android.nfc.Flags.nfcObserveMode()) {
+ Log.d(TAG, "observe mode isn't enabled");
return;
}
+
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ if (adapter == null) {
+ Log.e(TAG, "adapter is null, returning");
+ return;
+ }
+ ComponentName preferredService = mAidCache.getPreferredService();
+ boolean enableObserveMode = mServiceCache.doesServiceShouldDefaultToObserveMode(userId,
+ preferredService);
+ adapter.setObserveModeEnabled(enableObserveMode);
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
- ComponentName paymentService = getDefaultServiceForCategory(userId,
- CardEmulation.CATEGORY_PAYMENT, false);
- NfcManager manager = mContext.getSystemService(NfcManager.class);
- NfcAdapter adapter = manager.getDefaultAdapter();
- if (mServiceCache.doesServiceDefaultToObserveMode(userId,
- service != null ? service : paymentService)) {
- adapter.disallowTransaction();
- } else {
- adapter.allowTransaction();
- }
+ @Override
+ public void onWalletRoleHolderChanged(String holder, int userId) {
+ mPreferredServices.onWalletRoleHolderChanged(holder, userId);
+ mAidCache.onWalletRoleHolderChanged(holder, userId);
}
@Override
@@ -946,7 +1091,7 @@
public String getRegisteredAidCategory(String aid) {
RegisteredAidCache.AidResolveInfo resolvedInfo = mAidCache.resolveAid(aid);
if (resolvedInfo != null) {
- return resolvedInfo.category;
+ return resolvedInfo.getCategory();
}
return "";
}
@@ -954,4 +1099,8 @@
public boolean isRequiresScreenOnServiceExist() {
return mAidCache.isRequiresScreenOnServiceExist();
}
+
+ public boolean isPreferredServicePackageNameForUser(String packageName, int userId) {
+ return mAidCache.isPreferredServicePackageNameForUser(packageName, userId);
+ }
}
diff --git a/src/com/android/nfc/cardemulation/EnabledNfcFServices.java b/src/com/android/nfc/cardemulation/EnabledNfcFServices.java
index d7607d3..5857967 100644
--- a/src/com/android/nfc/cardemulation/EnabledNfcFServices.java
+++ b/src/com/android/nfc/cardemulation/EnabledNfcFServices.java
@@ -34,7 +34,7 @@
public class EnabledNfcFServices implements com.android.nfc.ForegroundUtils.Callback {
static final String TAG = "EnabledNfcFCardEmulationServices";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
final Context mContext;
final RegisteredNfcFServicesCache mNfcFServiceCache;
diff --git a/src/com/android/nfc/cardemulation/HostEmulationManager.java b/src/com/android/nfc/cardemulation/HostEmulationManager.java
index eb55ddb..1c31bde 100644
--- a/src/com/android/nfc/cardemulation/HostEmulationManager.java
+++ b/src/com/android/nfc/cardemulation/HostEmulationManager.java
@@ -16,6 +16,9 @@
package com.android.nfc.cardemulation;
+import android.annotation.TargetApi;
+import android.annotation.FlaggedApi;
+import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
import android.content.Context;
@@ -24,9 +27,11 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.nfc.NfcAdapter;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.PollingFrame;
import android.nfc.cardemulation.Utils;
import android.os.Bundle;
import android.os.Handler;
@@ -43,20 +48,30 @@
import androidx.annotation.VisibleForTesting;
+import com.android.nfc.NfcInjector;
import com.android.nfc.NfcService;
import com.android.nfc.NfcStatsLog;
import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo;
-import com.android.nfc.cardemulation.RegisteredServicesCache.DynamicSettings;
import com.android.nfc.cardemulation.util.StatsdUtils;
import com.android.nfc.flags.Flags;
+import com.android.nfc.proto.NfcEventProto;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+
public class HostEmulationManager {
static final String TAG = "HostEmulationManager";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final int STATE_IDLE = 0;
static final int STATE_W4_SELECT = 1;
@@ -74,6 +89,8 @@
static final byte INSTR_SELECT = (byte)0xA4;
static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345";
+ static final String NDEF_V1_AID = "D2760000850100";
+ static final String NDEF_V2_AID = "D2760000850101";
static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00};
static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82};
@@ -83,6 +100,8 @@
NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_PAYMENT;
static final int CE_HCE_OTHER =
NfcStatsLog.NFC_CARDEMULATION_OCCURRED__CATEGORY__HCE_OTHER;
+ static final String NFC_PACKAGE = "com.android.nfc";
+ static final String DATA_KEY = "data";
final Context mContext;
final RegisteredAidCache mAidCache;
@@ -102,13 +121,18 @@
boolean mServiceBound = false;
ComponentName mServiceName = null;
int mServiceUserId; // The UserId of the non-payment service
- ArrayList<Bundle> mPendingPollingLoopFrames = null;
+ ArrayList<PollingFrame> mPendingPollingLoopFrames = null;
+ private Map<Integer, Map<String, List<ApduServiceInfo>>> mPollingLoopFilters;
+ private Map<Integer, Map<Pattern, List<ApduServiceInfo>>> mPollingLoopPatternFilters;
// Variables below are for a payment service,
// which is typically bound persistently to improve on
// latency.
Messenger mPaymentService;
boolean mPaymentServiceBound = false;
+
+ boolean mEnableObserveModeAfterTransaction = false;
+ boolean mEnableObserveModeOnFieldOff = false;
ComponentName mPaymentServiceName = null;
int mPaymentServiceUserId; // The userId of the payment service
ComponentName mLastBoundPaymentServiceName;
@@ -124,23 +148,33 @@
String mLastSelectedAid;
int mState;
byte[] mSelectApdu;
+ Handler mHandler;
public HostEmulationManager(Context context, Looper looper, RegisteredAidCache aidCache) {
+ this(context, looper, aidCache, new StatsdUtils(StatsdUtils.SE_NAME_HCE));
+ }
+
+ @VisibleForTesting
+ HostEmulationManager(Context context, Looper looper, RegisteredAidCache aidCache,
+ StatsdUtils statsdUtils) {
mContext = context;
mLooper = looper;
+ mHandler = new Handler(looper);
mLock = new Object();
mAidCache = aidCache;
mState = STATE_IDLE;
mKeyguard = context.getSystemService(KeyguardManager.class);
mPowerManager = context.getSystemService(PowerManager.class);
- mStatsdUtils = Flags.statsdCeEventsFlag() ? new StatsdUtils(StatsdUtils.SE_NAME_HCE) : null;
+ mStatsdUtils = Flags.statsdCeEventsFlag() ? statsdUtils : null;
+ mPollingLoopFilters = new HashMap<Integer, Map<String, List<ApduServiceInfo>>>();
+ mPollingLoopPatternFilters = new HashMap<Integer, Map<Pattern, List<ApduServiceInfo>>>();
}
/**
* Preferred payment service changed
*/
public void onPreferredPaymentServiceChanged(int userId, final ComponentName service) {
- new Handler(mLooper).post(() -> {
+ mHandler.post(() -> {
synchronized (mLock) {
if (service != null) {
bindPaymentServiceLocked(userId, service);
@@ -154,37 +188,153 @@
private Messenger getForegroundServiceOrDefault() {
PackageManager packageManager = mContext.getPackageManager();
ComponentName preferredServiceName = mAidCache.getPreferredService();
- try {
- ApplicationInfo preferredServiceInfo =
- packageManager.getApplicationInfo(preferredServiceName.getPackageName(), 0);
- UserHandle user = UserHandle.getUserHandleForUid(preferredServiceInfo.uid);
- return bindServiceIfNeededLocked(user.getIdentifier(), preferredServiceName);
- } catch (NameNotFoundException nnfe) {
- Log.e(TAG, "Packange name not found, dropping polling frame", nnfe);
- unbindServiceIfNeededLocked();
+ if (preferredServiceName != null) {
+ try {
+ ApplicationInfo preferredServiceInfo =
+ packageManager.getApplicationInfo(preferredServiceName.getPackageName(), 0);
+ UserHandle user = UserHandle.getUserHandleForUid(preferredServiceInfo.uid);
+ return bindServiceIfNeededLocked(user.getIdentifier(), preferredServiceName);
+ } catch (NameNotFoundException nnfe) {
+ Log.e(TAG, "Packange name not found, dropping polling frame", nnfe);
+ unbindServiceIfNeededLocked();
+ }
}
return bindServiceIfNeededLocked(mPaymentServiceUserId, mPaymentServiceName);
}
- public void onPollingLoopDetected(Bundle pollingFrame) {
+ @TargetApi(35)
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void updatePollingLoopFilters(int userId, List<ApduServiceInfo> services) {
+ HashMap<String, List<ApduServiceInfo>> pollingLoopFilters =
+ new HashMap<String, List<ApduServiceInfo>>();
+ HashMap<Pattern, List<ApduServiceInfo>> pollingLoopPatternFilters =
+ new HashMap<Pattern, List<ApduServiceInfo>>();
+ for (ApduServiceInfo serviceInfo : services) {
+ for (String plf : serviceInfo.getPollingLoopFilters()) {
+ List<ApduServiceInfo> list =
+ pollingLoopFilters.getOrDefault(plf, new ArrayList<ApduServiceInfo>());
+ list.add(serviceInfo);
+ pollingLoopFilters.putIfAbsent(plf, list);
+
+ }
+ for (Pattern plpf : serviceInfo.getPollingLoopPatternFilters()) {
+ List<ApduServiceInfo> list =
+ pollingLoopPatternFilters.getOrDefault(plpf,
+ new ArrayList<ApduServiceInfo>());
+ list.add(serviceInfo);
+ pollingLoopPatternFilters.putIfAbsent(plpf, list);
+
+ }
+ }
+ mPollingLoopFilters.put(Integer.valueOf(userId), pollingLoopFilters);
+ mPollingLoopPatternFilters.put(Integer.valueOf(userId), pollingLoopPatternFilters);
+ }
+
+ @TargetApi(35)
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void onPollingLoopDetected(List<PollingFrame> pollingFrames) {
synchronized (mLock) {
if (mState == STATE_IDLE) {
mState = STATE_POLLING_LOOP;
}
- Messenger service = getForegroundServiceOrDefault();
- if (service != null) {
- ArrayList<Bundle> frames = new ArrayList<Bundle>();
- frames.add(pollingFrame);
- sendPollingFramesToServiceLocked(service, frames);
- } else {
- if (mPendingPollingLoopFrames == null) {
- mPendingPollingLoopFrames = new ArrayList<Bundle>(1);
- }
+ int onCount = 0;
+ if (mPendingPollingLoopFrames == null) {
+ mPendingPollingLoopFrames = new ArrayList<PollingFrame>(1);
+ }
+ Messenger service = null;
+ for (PollingFrame pollingFrame : pollingFrames) {
mPendingPollingLoopFrames.add(pollingFrame);
+ if (pollingFrame.getType()
+ == PollingFrame.POLLING_LOOP_TYPE_F) {
+ service = getForegroundServiceOrDefault();
+ } else if (pollingFrame.getType()
+ == PollingFrame.POLLING_LOOP_TYPE_UNKNOWN) {
+ byte[] data = pollingFrame.getData();
+ String dataStr = HexFormat.of().formatHex(data).toUpperCase(Locale.ROOT);
+ List<ApduServiceInfo> serviceInfos =
+ mPollingLoopFilters.get(ActivityManager.getCurrentUser()).get(dataStr);
+ Map<Pattern, List<ApduServiceInfo>> patternMappingForUser =
+ mPollingLoopPatternFilters.get(ActivityManager.getCurrentUser());
+ Set<Pattern> patternSet = patternMappingForUser.keySet();
+ List<Pattern> matchedPatterns = patternSet.stream()
+ .filter(p -> p.matcher(dataStr).matches()).toList();
+ if (!matchedPatterns.isEmpty()) {
+ if (service == null) {
+ serviceInfos = new ArrayList<ApduServiceInfo>();
+ }
+ for (Pattern matchedPattern : matchedPatterns) {
+ serviceInfos.addAll(patternMappingForUser.get(matchedPattern));
+ }
+ }
+ if (serviceInfos != null && serviceInfos.size() > 0) {
+ ApduServiceInfo serviceInfo;
+ if (serviceInfos.size() == 1) {
+ serviceInfo = serviceInfos.get(0);
+ } else {
+ serviceInfo = mAidCache.resolvePollingLoopFilterConflict(serviceInfos);
+ if (serviceInfo == null) {
+ /* If neither the foreground or payments service can handle the plf,
+ * pick the first in the list. */
+ serviceInfo = serviceInfos.get(0);
+ }
+ }
+ if (serviceInfo.getShouldAutoTransact(dataStr)) {
+ allowOneTransaction();
+ pollingFrame.setTriggeredAutoTransact(true);
+ }
+ UserHandle user = UserHandle.getUserHandleForUid(serviceInfo.getUid());
+ service = bindServiceIfNeededLocked(user.getIdentifier(),
+ serviceInfo.getComponent());
+ } else {
+ service = getForegroundServiceOrDefault();
+ }
+
+ if (mStatsdUtils != null) {
+ mStatsdUtils.tallyPollingFrame(dataStr, pollingFrame);
+ }
+ }
+ if (mStatsdUtils != null) {
+ mStatsdUtils.logPollingFrames();
+ }
+ }
+
+ if (service == null) {
+ if (mActiveService != null) {
+ service = mActiveService;
+ } else if (mPendingPollingLoopFrames.size() >= 4) {
+ loop_on_off: for (PollingFrame frame : mPendingPollingLoopFrames) {
+ int type = frame.getType();
+ switch (type) {
+ case PollingFrame.POLLING_LOOP_TYPE_ON:
+ onCount++;
+ break;
+ case PollingFrame.POLLING_LOOP_TYPE_OFF:
+ // Send the loop data if we've seen at least one on before an off.
+ if (onCount >=1) {
+ service = getForegroundServiceOrDefault();
+ break loop_on_off;
+ }
+ break;
+ default:
+ }
+ }
+ }
+ }
+
+ if (service != null) {
+ sendPollingFramesToServiceLocked(service, mPendingPollingLoopFrames);
+ mPendingPollingLoopFrames = null;
}
}
}
+ private void allowOneTransaction() {
+ Log.d(TAG, "disabling observe mode for one transaction.");
+ mEnableObserveModeAfterTransaction = true;
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ adapter.setObserveModeEnabled(false);
+ }
+
/**
* Preferred foreground service changed
*/
@@ -198,24 +348,53 @@
}
}
+ public void onFieldChangeDetected(boolean fieldOn) {
+ if (!fieldOn && mEnableObserveModeOnFieldOff && mEnableObserveModeAfterTransaction) {
+ Log.d(TAG, "re-enabling observe mode after NFC Field off.");
+ mEnableObserveModeAfterTransaction = false;
+ mEnableObserveModeOnFieldOff = false;
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ adapter.setObserveModeEnabled(true);
+ }
+ }
+
public void onHostEmulationActivated() {
- Log.d(TAG, "notifyHostEmulationActivated");
synchronized (mLock) {
// Regardless of what happens, if we're having a tap again
// activity up, close it
Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
- intent.setPackage("com.android.nfc");
+ intent.setPackage(NFC_PACKAGE);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
if (mState != STATE_IDLE) {
Log.e(TAG, "Got activation event in non-idle state");
}
mState = STATE_W4_SELECT;
}
- if (Flags.testFlag()) {
- Log.v(TAG, "Test feature flag enabled");
+ }
+
+ static private class UnroutableAidBugReportRunnable implements Runnable {
+ List<String> mUnroutedAids;
+
+ UnroutableAidBugReportRunnable(String aid) {
+ mUnroutedAids = new ArrayList<String>(1);
+ mUnroutedAids.add(aid);
+ }
+
+ void addAid(String aid) {
+ mUnroutedAids.add(aid);
+ }
+ @Override
+ public void run() {
+ NfcService.getInstance().mNfcDiagnostics.takeBugReport(
+ "NFC tap failed."
+ + " (If you weren't using NFC, "
+ + "no need to submit this report.)",
+ "Couldn't route " + String.join(", ", mUnroutedAids));
}
}
+ UnroutableAidBugReportRunnable mUnroutableAidBugReportRunnable = null;
+
public void onHostEmulationData(byte[] data) {
Log.d(TAG, "notifyHostEmulationData");
String selectAid = findSelectAid(data);
@@ -237,9 +416,37 @@
}
resolveInfo = mAidCache.resolveAid(selectAid);
if (resolveInfo == null || resolveInfo.services.size() == 0) {
+ if (selectAid.equals(NDEF_V1_AID) || selectAid.equals(NDEF_V2_AID)) {
+ Log.w(TAG, "Can't route NDEF AID, sending AID_NOT_FOUND");
+ } else if (!mPowerManager.isScreenOn()) {
+ Log.i(TAG,
+ "Screen is off, sending AID_NOT_FOUND, but not triggering bug report");
+ } else {
+ Log.w(TAG, "Can't handle AID " + selectAid + " sending AID_NOT_FOUND");
+ if (mUnroutableAidBugReportRunnable != null) {
+ mUnroutableAidBugReportRunnable.addAid(selectAid);
+ } else {
+ mUnroutableAidBugReportRunnable =
+ new UnroutableAidBugReportRunnable(selectAid);
+ /* Wait 1s to see if there is an alternate AID we can route before
+ * taking a bug report */
+ mHandler.postDelayed(mUnroutableAidBugReportRunnable, 1000);
+ }
+ }
+ NfcInjector.getInstance().getNfcEventLog().logEvent(
+ NfcEventProto.EventType.newBuilder()
+ .setCeUnroutableAid(
+ NfcEventProto.NfcCeUnroutableAid.newBuilder()
+ .setAid(selectAid)
+ .build())
+ .build());
// Tell the remote we don't handle this AID
NfcService.getInstance().sendData(AID_NOT_FOUND);
return;
+ } else if (mUnroutableAidBugReportRunnable != null) {
+ /* If there is a pending bug report runnable, cancel it. */
+ mHandler.removeCallbacks(mUnroutableAidBugReportRunnable);
+ mUnroutableAidBugReportRunnable = null;
}
mLastSelectedAid = selectAid;
if (resolveInfo.defaultService != null) {
@@ -343,7 +550,6 @@
statsdCategory,
"HCE",
uid);
- Log.d(TAG, "StatsdCeEventsFlag disabled logged: " + statsdCategory);
}
} else {
Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT");
@@ -389,9 +595,17 @@
mActiveService = null;
mActiveServiceName = null;
mActiveServiceUserId = -1;
+ mPendingPollingLoopFrames = null;
unbindServiceIfNeededLocked();
mState = STATE_IDLE;
+ if (mEnableObserveModeAfterTransaction) {
+ Log.d(TAG, "re-enabling observe mode after HCE deactivation");
+ mEnableObserveModeAfterTransaction = false;
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ adapter.setObserveModeEnabled(true);
+ }
+
if (mStatsdUtils != null) {
mStatsdUtils.logCardEmulationDeactivatedEvent();
}
@@ -406,6 +620,10 @@
} else {
sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
}
+ if (mEnableObserveModeAfterTransaction) {
+ Log.i(TAG, "OffHost AID selected, waiting for Field off to reenable observe mode");
+ mEnableObserveModeOnFieldOff = true;
+ }
mActiveService = null;
mActiveServiceName = null;
mActiveServiceUserId = -1;
@@ -414,12 +632,16 @@
//close the TapAgainDialog
Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE);
- intent.setPackage("com.android.nfc");
+ intent.setPackage(NFC_PACKAGE);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
Messenger bindServiceIfNeededLocked(int userId, ComponentName service) {
+ if (service == null) {
+ Log.e(TAG, "service ComponentName is null");
+ return null;
+ }
if (mPaymentServiceName != null && mPaymentServiceName.equals(service)
&& mPaymentServiceUserId == userId) {
Log.d(TAG, "Service already bound as payment service.");
@@ -466,7 +688,7 @@
}
Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU);
Bundle dataBundle = new Bundle();
- dataBundle.putByteArray("data", data);
+ dataBundle.putByteArray(DATA_KEY, data);
msg.setData(dataBundle);
msg.replyTo = mMessenger;
try {
@@ -476,7 +698,8 @@
}
}
- void sendPollingFramesToServiceLocked(Messenger service, ArrayList<Bundle> frames) {
+ void sendPollingFramesToServiceLocked(Messenger service,
+ ArrayList<PollingFrame> pollingFrames) {
if (!Objects.equals(service, mActiveService)) {
sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED);
mActiveService = service;
@@ -490,7 +713,8 @@
}
Message msg = Message.obtain(null, HostApduService.MSG_POLLING_LOOP);
Bundle msgData = new Bundle();
- msgData.putParcelableArrayList(HostApduService.POLLING_LOOP_FRAMES_BUNDLE_KEY, frames);
+ msgData.putParcelableArrayList(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE,
+ pollingFrames);
msg.setData(msgData);
msg.replyTo = mMessenger;
if (mState == STATE_IDLE) {
@@ -515,13 +739,19 @@
}
void unbindPaymentServiceLocked() {
+ Log.d(TAG, "Unbinding payment service");
if (mPaymentServiceBound) {
- mContext.unbindService(mPaymentConnection);
+ try {
+ mContext.unbindService(mPaymentConnection);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to unbind payment service: " + mPaymentServiceName, e);
+ }
mPaymentServiceBound = false;
- mPaymentService = null;
- mPaymentServiceName = null;
- mPaymentServiceUserId = -1;
}
+
+ mPaymentService = null;
+ mPaymentServiceName = null;
+ mPaymentServiceUserId = -1;
}
void bindPaymentServiceLocked(int userId, ComponentName service) {
@@ -548,12 +778,17 @@
void unbindServiceIfNeededLocked() {
if (mServiceBound) {
Log.d(TAG, "Unbinding from service " + mServiceName);
- mContext.unbindService(mConnection);
+ try {
+ mContext.unbindService(mConnection);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to unbind service " + mServiceName, e);
+ }
mServiceBound = false;
- mService = null;
- mServiceName = null;
- mServiceUserId = -1;
}
+
+ mService = null;
+ mServiceName = null;
+ mServiceUserId = -1;
}
void launchTapAgain(ApduServiceInfo service, String category) {
@@ -607,20 +842,22 @@
synchronized (mLock) {
/* Preferred Payment Service has been changed. */
if (!mLastBoundPaymentServiceName.equals(name)) {
+ Log.i(TAG, "Ignoring bound payment service, " + name + " != "
+ + mLastBoundPaymentServiceName);
return;
}
mPaymentServiceName = name;
mPaymentService = new Messenger(service);
+ Log.i(TAG, "Payment service bound: " + name);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Payment service disconnected: " + name);
synchronized (mLock) {
mPaymentService = null;
- mPaymentServiceBound = false;
mPaymentServiceName = null;
- mPaymentServiceUserId = -1;
}
}
};
@@ -636,7 +873,7 @@
mService = new Messenger(service);
mServiceName = name;
mServiceBound = true;
- Log.d(TAG, "Service bound");
+ Log.d(TAG, "Service bound: " + name);
mState = STATE_XFER;
// Send pending select APDU
if (mSelectApdu != null) {
@@ -648,6 +885,8 @@
} else if (mPendingPollingLoopFrames != null) {
sendPollingFramesToServiceLocked(mService, mPendingPollingLoopFrames);
mPendingPollingLoopFrames = null;
+ } else {
+ Log.d(TAG, "bound with nothing to send");
}
}
}
@@ -655,11 +894,10 @@
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
- Log.d(TAG, "Service unbound");
+ Log.d(TAG, "Service unbound: " + name);
mService = null;
mServiceName = null;
mServiceBound = false;
- mServiceUserId = -1;
}
}
};
@@ -681,7 +919,7 @@
if (dataBundle == null) {
return;
}
- byte[] data = dataBundle.getByteArray("data");
+ byte[] data = dataBundle.getByteArray(DATA_KEY);
if (data == null || data.length == 0) {
Log.e(TAG, "Dropping empty R-APDU");
return;
@@ -767,6 +1005,11 @@
}
@VisibleForTesting
+ public ServiceConnection getPaymentConnection(){
+ return mPaymentConnection;
+ }
+
+ @VisibleForTesting
public IBinder getMessenger(){
if (mActiveService != null) {
return mActiveService.getBinder();
@@ -775,6 +1018,11 @@
}
@VisibleForTesting
+ public Messenger getLocalMessenger() {
+ return mMessenger;
+ }
+
+ @VisibleForTesting
public ComponentName getServiceName(){
return mLastBoundPaymentServiceName;
}
@@ -783,4 +1031,14 @@
public Boolean isServiceBounded(){
return mServiceBound;
}
+
+ @VisibleForTesting
+ public Map<Integer, Map<String, List<ApduServiceInfo>>> getPollingLoopFilters() {
+ return mPollingLoopFilters;
+ }
+
+ @VisibleForTesting
+ public Map<Integer, Map<Pattern, List<ApduServiceInfo>>> getPollingLoopPatternFilters() {
+ return mPollingLoopPatternFilters;
+ }
}
diff --git a/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java b/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java
index 1aa9b32..f7cf14b 100644
--- a/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java
+++ b/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java
@@ -45,7 +45,7 @@
public class HostNfcFEmulationManager {
static final String TAG = "HostNfcFEmulationManager";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final int STATE_IDLE = 0;
static final int STATE_W4_SERVICE = 1;
diff --git a/src/com/android/nfc/cardemulation/PreferredServices.java b/src/com/android/nfc/cardemulation/PreferredServices.java
index e3c91a0..2e22dd5 100644
--- a/src/com/android/nfc/cardemulation/PreferredServices.java
+++ b/src/com/android/nfc/cardemulation/PreferredServices.java
@@ -15,6 +15,7 @@
*/
package com.android.nfc.cardemulation;
+import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
@@ -24,10 +25,13 @@
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.Utils;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.flags.Flags;
+
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.sysprop.NfcProperties;
@@ -38,7 +42,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* This class keeps track of what HCE/SE-based services are
@@ -58,7 +64,7 @@
*/
public class PreferredServices implements com.android.nfc.ForegroundUtils.Callback {
static final String TAG = "PreferredCardEmulationServices";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final Uri paymentDefaultUri = Settings.Secure.getUriFor(
Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
static final Uri paymentForegroundUri = Settings.Secure.getUriFor(
@@ -66,6 +72,7 @@
final SettingsObserver mSettingsObserver;
final Context mContext;
+ final WalletRoleObserver mWalletRoleObserver;
final RegisteredServicesCache mServiceCache;
final RegisteredAidCache mAidCache;
final Callback mCallback;
@@ -93,6 +100,10 @@
ComponentName mForegroundCurrent; // The currently computed foreground component
int mForegroundCurrentUid; // The UID of the currently computed foreground component
+ ComponentName mDefaultWalletHolderPaymentService;
+
+ int mUserIdDefaultWalletHolder;
+
public interface Callback {
/**
* Notify when preferred payment service is changed
@@ -105,8 +116,10 @@
}
public PreferredServices(Context context, RegisteredServicesCache serviceCache,
- RegisteredAidCache aidCache, Callback callback) {
+ RegisteredAidCache aidCache, WalletRoleObserver walletRoleObserver,
+ Callback callback) {
mContext = context;
+ mWalletRoleObserver = walletRoleObserver;
mForegroundUtils = ForegroundUtils.getInstance(
context.getSystemService(ActivityManager.class));
mServiceCache = serviceCache;
@@ -121,8 +134,15 @@
paymentForegroundUri,
true, mSettingsObserver, UserHandle.ALL);
+ int currentUserId = ActivityManager.getCurrentUser();
+
// Load current settings defaults for payments
- loadDefaultsFromSettings(ActivityManager.getCurrentUser(), false);
+ loadDefaultsFromSettings(currentUserId, false);
+
+ if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ String holder = mWalletRoleObserver.getDefaultWalletRoleHolder(currentUserId);
+ onWalletRoleHolderChanged(holder, currentUserId);
+ }
}
private final class SettingsObserver extends ContentObserver {
@@ -141,6 +161,42 @@
}
};
+ @TargetApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public void onWalletRoleHolderChanged(String defaultWalletHolderPackageName, int userId) {
+ if (defaultWalletHolderPackageName == null) {
+ mDefaultWalletHolderPaymentService = null;
+ mCallback.onPreferredPaymentServiceChanged(userId, null);
+ return;
+ }
+ List<ApduServiceInfo> serviceInfos = mServiceCache.getInstalledServices(userId);
+ List<ComponentName> roleHolderPaymentServices = new ArrayList<>();
+ int servicesCount = serviceInfos.size();
+ for(int i = 0; i < servicesCount; i++) {
+ ApduServiceInfo serviceInfo = serviceInfos.get(i);
+ ComponentName componentName = serviceInfo.getComponent();
+ if (componentName.getPackageName()
+ .equals(defaultWalletHolderPackageName)) {
+ List<String> aids = serviceInfo.getAids();
+ int aidsCount = aids.size();
+ for (int j = 0; j < aidsCount; j++) {
+ String aid = aids.get(j);
+ if (serviceInfo.getCategoryForAid(aid)
+ .equals(CardEmulation.CATEGORY_PAYMENT)) {
+ roleHolderPaymentServices.add(componentName);
+ break;
+ }
+ }
+ }
+ }
+ mUserIdDefaultWalletHolder = userId;
+ ComponentName candidate = !roleHolderPaymentServices.isEmpty()
+ ? roleHolderPaymentServices.get(0) : null;
+ if (!Objects.equals(candidate, mDefaultWalletHolderPaymentService)) {
+ mCallback.onPreferredPaymentServiceChanged(userId, candidate);
+ }
+ mDefaultWalletHolderPaymentService = candidate;
+ }
+
void loadDefaultsFromSettings(int userId, boolean force) {
boolean paymentDefaultChanged = false;
boolean paymentPreferForegroundChanged = false;
@@ -155,9 +211,15 @@
UserHandle newUser = null;
// search for default payment setting within enabled profiles
for (UserHandle uh : userHandles) {
- name = Settings.Secure.getString(
- mContext.createContextAsUser(uh, 0).getContentResolver(),
- Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
+ try {
+ name = Settings.Secure.getString(
+ mContext.createContextAsUser(uh, 0).getContentResolver(),
+ Constants.SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT);
+ } catch (IllegalStateException e) {
+ Log.d(TAG, "Fail to get PackageManager for user: " + uh);
+ continue;
+ }
+
if (name != null) {
newUser = uh;
newDefaultName = name;
@@ -179,8 +241,9 @@
boolean preferForeground = false;
try {
// get the setting from the main user instead of from the user profiles.
- preferForeground = Settings.Secure.getInt(mContext
- .createContextAsUser(currentUser, 0).getContentResolver(),
+ preferForeground = mWalletRoleObserver.isWalletRoleFeatureEnabled()
+ || Settings.Secure.getInt(mContext
+ .createContextAsUser(currentUser, 0).getContentResolver(),
Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
} catch (SettingNotFoundException e) {
}
@@ -203,7 +266,7 @@
}
}
// Notify if anything changed
- if (paymentDefaultChanged || force) {
+ if (!mWalletRoleObserver.isWalletRoleFeatureEnabled() && (paymentDefaultChanged || force)) {
mCallback.onPreferredPaymentServiceChanged(newUser.getIdentifier(), newDefault);
}
if (paymentPreferForegroundChanged || force) {
diff --git a/src/com/android/nfc/cardemulation/RegisteredAidCache.java b/src/com/android/nfc/cardemulation/RegisteredAidCache.java
index 3cb220f..6aa98a1 100644
--- a/src/com/android/nfc/cardemulation/RegisteredAidCache.java
+++ b/src/com/android/nfc/cardemulation/RegisteredAidCache.java
@@ -16,13 +16,14 @@
package com.android.nfc.cardemulation;
+import android.annotation.TargetApi;
+import android.annotation.FlaggedApi;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.nfc.cardemulation.Utils;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.NfcProperties;
@@ -31,6 +32,8 @@
import com.android.nfc.NfcService;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -43,12 +46,12 @@
import java.util.NavigableMap;
import java.util.PriorityQueue;
import java.util.TreeMap;
-import androidx.annotation.VisibleForTesting;
public class RegisteredAidCache {
static final String TAG = "RegisteredAidCache";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
+ private static final boolean VDBG = false; // turn on for local testing.
static final int AID_ROUTE_QUAL_SUBSET = 0x20;
static final int AID_ROUTE_QUAL_PREFIX = 0x10;
@@ -136,11 +139,17 @@
", mustRoute=" + mustRoute +
'}';
}
+
+ String getCategory() {
+ return category;
+ }
}
final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo();
final Context mContext;
+
+ final WalletRoleObserver mWalletRoleObserver;
final AidRoutingManager mRoutingManager;
final Object mLock = new Object();
@@ -150,19 +159,25 @@
ComponentName mPreferredForegroundService;
int mUserIdPreferredForegroundService;
+ String mDefaultWalletHolderPackageName;
+
+ int mUserIdDefaultWalletHolder;
+
boolean mNfcEnabled = false;
boolean mSupportsPrefixes = false;
boolean mSupportsSubset = false;
boolean mRequiresScreenOnServiceExist = false;
- public RegisteredAidCache(Context context) {
- this(context, new AidRoutingManager());
+ public RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver) {
+ this(context, walletRoleObserver, new AidRoutingManager());
}
@VisibleForTesting
- public RegisteredAidCache(Context context, AidRoutingManager routingManager) {
+ public RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver,
+ AidRoutingManager routingManager) {
mContext = context;
- mRoutingManager = routingManager ;
+ mWalletRoleObserver = walletRoleObserver;
+ mRoutingManager = routingManager;
mPreferredPaymentService = null;
mUserIdPreferredPaymentService = -1;
mPreferredForegroundService = null;
@@ -246,12 +261,12 @@
resolveInfo.services.size() == 0) {
return false;
}
-
if (resolveInfo.defaultService != null) {
return service.equals(resolveInfo.defaultService.getComponent());
} else if (resolveInfo.services.size() == 1) {
return service.equals(resolveInfo.services.get(0).getComponent());
} else {
+ Log.d(TAG, "Not Default Service: " + service.getClassName());
// More than one service, not the default
return false;
}
@@ -261,6 +276,75 @@
return mRequiresScreenOnServiceExist;
}
+ @TargetApi(35)
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ ApduServiceInfo resolvePollingLoopFilterConflict(List<ApduServiceInfo> conflictingServices) {
+ ApduServiceInfo matchedForeground = null;
+ List<ApduServiceInfo> roleHolderServices = new ArrayList<>();
+ ApduServiceInfo matchedPayment = null;
+ for (ApduServiceInfo serviceInfo : conflictingServices) {
+ int userId = UserHandle.getUserHandleForUid(serviceInfo.getUid())
+ .getIdentifier();
+ ComponentName componentName = serviceInfo.getComponent();
+
+ if (componentName.equals(mPreferredForegroundService) &&
+ userId == mUserIdPreferredForegroundService) {
+ matchedForeground = serviceInfo;
+ } else if(mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ if (userId == mUserIdDefaultWalletHolder &&
+ componentName.getPackageName().equals(mDefaultWalletHolderPackageName)) {
+ roleHolderServices.add(serviceInfo);
+ }
+ } else if (componentName.equals(mPreferredPaymentService) &&
+ userId == mUserIdPreferredPaymentService) {
+ matchedPayment = serviceInfo;
+ }
+ }
+ if (matchedForeground != null) {
+ return matchedForeground;
+ }
+ if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ roleHolderServices.sort((o1, o2) ->
+ String.CASE_INSENSITIVE_ORDER.compare(o1.getComponent().toShortString(),
+ o2.getComponent().toShortString()));
+ return roleHolderServices.isEmpty() ? null : roleHolderServices.get(0);
+ }
+ return matchedPayment;
+ }
+
+ private static void nonDefaultResolution(boolean serviceClaimsPaymentAid,
+ ServiceAidInfo serviceAidInfo, AidResolveInfo resolveInfo) {
+ if (serviceClaimsPaymentAid) {
+ // If this service claims it's a payment AID, don't route it,
+ // because it's not the default. Otherwise, add it to the list
+ // but not as default.
+ if (VDBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
+ serviceAidInfo.service.getComponent() +
+ " because it's not the payment default.)");
+ } else {
+ if (serviceAidInfo.service.isCategoryOtherServiceEnabled()) {
+ if (VDBG) Log.d(TAG, "resolveAidLocked: " + serviceAidInfo.service.getComponent() +
+ " is selected other service");
+ resolveInfo.services.add(serviceAidInfo.service);
+ }
+ }
+ }
+
+ private static void nonDefaultRouting(AidResolveInfo resolveInfo,
+ boolean makeSingleServiceDefault) {
+ if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
+ if (DBG) Log.d(TAG,
+ "resolveAidLocked: DECISION: making single handling service " +
+ resolveInfo.services.get(0).getComponent() + " default.");
+ resolveInfo.defaultService = resolveInfo.services.get(0);
+ } else {
+ // Nothing to do, all services already in list
+ if (DBG) {
+ Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
+ }
+ }
+ }
+
/**
* Resolves a conflict between multiple services handling the same
* AIDs. Note that the AID itself is not an input to the decision
@@ -269,8 +353,9 @@
* this:
*
* 1) If there is a preferred foreground service, that service wins
- * 2) Else, if there is a preferred payment service, that service wins
- * 3) Else, if there is no winner, and all conflicting services will be
+ * 2) Else if there is a default wallet app, that app wins
+ * 3) Else, if there is a preferred payment service, that service wins
+ * 4) Else, if there is no winner, and all conflicting services will be
* in the list of resolved services.
*/
AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices,
@@ -284,6 +369,8 @@
ApduServiceInfo matchedForeground = null;
ApduServiceInfo matchedPayment = null;
+ List<ApduServiceInfo> defaultWalletServices = new ArrayList<>();
+
for (ServiceAidInfo serviceAidInfo : conflictingServices) {
boolean serviceClaimsPaymentAid =
CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
@@ -293,31 +380,34 @@
if (componentName.equals(mPreferredForegroundService) &&
userId == mUserIdPreferredForegroundService) {
+ if (VDBG) Log.d(TAG, "Prioritizing foreground services.");
resolveInfo.services.add(serviceAidInfo.service);
if (serviceClaimsPaymentAid) {
resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
}
matchedForeground = serviceAidInfo.service;
- } else if (componentName.equals(mPreferredPaymentService) &&
- userId == mUserIdPreferredPaymentService &&
- serviceClaimsPaymentAid) {
+ } else if(mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ if(userId == mUserIdDefaultWalletHolder
+ && componentName.getPackageName().equals(
+ mDefaultWalletHolderPackageName)) {
+ if (VDBG) Log.d(TAG, "Prioritizing default wallet services.");
+ resolveInfo.services.add(serviceAidInfo.service);
+ if (serviceClaimsPaymentAid) {
+ resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ }
+ defaultWalletServices.add(serviceAidInfo.service);
+ } else {
+ nonDefaultResolution(serviceClaimsPaymentAid, serviceAidInfo, resolveInfo);
+ }
+ } else {
+ if (componentName.equals(mPreferredPaymentService)
+ && userId == mUserIdPreferredPaymentService && serviceClaimsPaymentAid) {
+ if (DBG) Log.d(TAG, "Prioritizing dpp services.");
resolveInfo.services.add(serviceAidInfo.service);
resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
matchedPayment = serviceAidInfo.service;
- } else {
- if (serviceClaimsPaymentAid) {
- // If this service claims it's a payment AID, don't route it,
- // because it's not the default. Otherwise, add it to the list
- // but not as default.
- if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
- serviceAidInfo.service.getComponent() +
- " because it's not the payment default.)");
} else {
- if (serviceAidInfo.service.isCategoryOtherServiceEnabled()) {
- if (DBG) Log.d(TAG, serviceAidInfo.service.getComponent() +
- " is selected other service");
- resolveInfo.services.add(serviceAidInfo.service);
- }
+ nonDefaultResolution(serviceClaimsPaymentAid, serviceAidInfo, resolveInfo);
}
}
}
@@ -327,21 +417,30 @@
if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " +
matchedForeground);
resolveInfo.defaultService = matchedForeground;
+
+ // Wallet Role Holder and the PreferredPaymentService are mutually exclusive. If the wallet
+ // role feature is enabled, the matched payment check should not take place at all.
+ } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled() &&
+ !defaultWalletServices.isEmpty()) {
+ // 2nd priority: if there is a default wallet application with services that
+ // claim this AID, that application gets it.
+ if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to default wallet " +
+ mDefaultWalletHolderPackageName);
+ // If the role holder has multiple services with the same AID type, then we select
+ // the first one. The services are sorted alphabetically based on their component
+ // names.
+ defaultWalletServices.sort((o1, o2) ->
+ String.CASE_INSENSITIVE_ORDER.compare(o1.getComponent().toShortString(),
+ o2.getComponent().toShortString()));
+ resolveInfo.defaultService = defaultWalletServices.get(0);
} else if (matchedPayment != null) {
- // 2nd priority: if there is a preferred payment service,
+ // 3d priority: if there is a preferred payment service,
// and that service claims this as a payment AID, that service gets it
if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " +
"default " + matchedPayment);
resolveInfo.defaultService = matchedPayment;
} else {
- if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
- if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " +
- resolveInfo.services.get(0).getComponent() + " default.");
- resolveInfo.defaultService = resolveInfo.services.get(0);
- } else {
- // Nothing to do, all services already in list
- if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
- }
+ nonDefaultRouting(resolveInfo, makeSingleServiceDefault);
}
return resolveInfo;
}
@@ -349,6 +448,7 @@
class DefaultServiceInfo {
ServiceAidInfo paymentDefault;
ServiceAidInfo foregroundDefault;
+ List<ServiceAidInfo> walletDefaults = new ArrayList<>();
}
DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) {
@@ -364,7 +464,12 @@
if (componentName.equals(mPreferredForegroundService) &&
userId == mUserIdPreferredForegroundService) {
defaultServiceInfo.foregroundDefault = serviceAidInfo;
- } else if (componentName.equals(mPreferredPaymentService) &&
+ } else if(mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ if(userId == mUserIdDefaultWalletHolder && componentName
+ .getPackageName().equals(mDefaultWalletHolderPackageName)) {
+ defaultServiceInfo.walletDefaults.add(serviceAidInfo);
+ }
+ }else if (componentName.equals(mPreferredPaymentService) &&
userId == mUserIdPreferredPaymentService &&
serviceClaimsPaymentAid) {
defaultServiceInfo.paymentDefault = serviceAidInfo;
@@ -373,6 +478,27 @@
return defaultServiceInfo;
}
+ private AidResolveInfo noChildrenAidsPreferred(ArrayList<ServiceAidInfo> aidServices,
+ ArrayList<ServiceAidInfo> conflictingServices) {
+ // No children that are preferred; add all services of the root
+ // make single service default if no children are present
+ if (DBG) Log.d(TAG, "No service has preference, adding all.");
+ AidResolveInfo resolveinfo =
+ resolveAidConflictLocked(aidServices, conflictingServices.isEmpty());
+ //If the AID is subsetAID check for conflicting prefix in all
+ //conflciting services and root services.
+ if (isSubset(aidServices.get(0).aid)) {
+ ArrayList<ApduServiceInfo> apduServiceList = new ArrayList<ApduServiceInfo>();
+ for (ServiceAidInfo serviceInfo : conflictingServices)
+ apduServiceList.add(serviceInfo.service);
+ for (ServiceAidInfo serviceInfo : aidServices)
+ apduServiceList.add(serviceInfo.service);
+ resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(
+ aidServices.get(0).aid, apduServiceList, false);
+ }
+ return resolveinfo;
+ }
+
AidResolveInfo resolveAidConflictLocked(ArrayList<ServiceAidInfo> aidServices,
ArrayList<ServiceAidInfo> conflictingServices) {
// Find defaults among the root AID services themselves
@@ -383,9 +509,11 @@
AidResolveInfo resolveinfo;
// Three conditions under which the root AID gets to be the default
// 1. A service registering the root AID is the current foreground preferred
- // 2. A service registering the root AID is the current tap & pay default AND
+ // 2. A service registering the root AID is the wallet role holder AND no child
+ // child is the current foreground preferred
+ // 3. A service registering the root AID is the current tap & pay default AND
// no child is the current foreground preferred
- // 3. There is only one service for the root AID, and there are no children
+ // 4. There is only one service for the root AID, and there are no children
if (aidDefaultInfo.foregroundDefault != null) {
if (DBG) Log.d(TAG, "Prefix AID service " +
aidDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +
@@ -399,6 +527,37 @@
List.of(resolveinfo.defaultService), true);
}
return resolveinfo;
+ } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ if(!aidDefaultInfo.walletDefaults.isEmpty()) {
+ // Check if any of the conflicting services is foreground default
+ if (conflictingDefaultInfo.foregroundDefault != null) {
+ // Conflicting AID registration is in foreground, trumps prefix tap&pay default
+ if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " +
+ "preferred, ignoring prefix.");
+ return EMPTY_RESOLVE_INFO;
+ } else {
+ // Prefix service is default wallet, treat as normal AID conflict for just prefix
+ if (DBG) Log.d(TAG, "Default wallet app exists. ignoring conflicting AIDs.");
+ resolveinfo = resolveAidConflictLocked(aidServices, true);
+ //If the AID is subsetAID check for prefix in all services.
+ if (isSubset(aidServices.get(0).aid)) {
+ resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(
+ aidServices.get(0).aid,
+ List.of(resolveinfo.defaultService), true);
+ }
+ return resolveinfo;
+ }
+ } else {
+ if (conflictingDefaultInfo.foregroundDefault != null ||
+ !conflictingDefaultInfo.walletDefaults.isEmpty()) {
+ if (DBG) Log.d(TAG,
+ "One of the conflicting AID registrations is wallet holder " +
+ "or foreground preferred, ignoring prefix.");
+ return EMPTY_RESOLVE_INFO;
+ } else {
+ return noChildrenAidsPreferred(aidServices, conflictingServices);
+ }
+ }
} else if (aidDefaultInfo.paymentDefault != null) {
// Check if any of the conflicting services is foreground default
if (conflictingDefaultInfo.foregroundDefault != null) {
@@ -426,22 +585,7 @@
"default or foreground preferred, ignoring prefix.");
return EMPTY_RESOLVE_INFO;
} else {
- // No children that are preferred; add all services of the root
- // make single service default if no children are present
- if (DBG) Log.d(TAG, "No service has preference, adding all.");
- resolveinfo = resolveAidConflictLocked(aidServices, conflictingServices.isEmpty());
- //If the AID is subsetAID check for conflicting prefix in all
- //conflciting services and root services.
- if (isSubset(aidServices.get(0).aid)) {
- ArrayList<ApduServiceInfo> apduServiceList = new ArrayList<ApduServiceInfo>();
- for (ServiceAidInfo serviceInfo : conflictingServices)
- apduServiceList.add(serviceInfo.service);
- for (ServiceAidInfo serviceInfo : aidServices)
- apduServiceList.add(serviceInfo.service);
- resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(
- aidServices.get(0).aid, apduServiceList, false);
- }
- return resolveinfo;
+ return noChildrenAidsPreferred(aidServices, conflictingServices);
}
}
}
@@ -477,7 +621,7 @@
continue;
}
for (ApduServiceInfo service : entry.getValue()) {
- if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
+ if (VDBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
List<String> prefixAids = service.getPrefixAids();
List<String> subSetAids = service.getSubsetAids();
@@ -869,7 +1013,11 @@
reversedQueue.removeAll(resolvedAids);
resolvedAids.clear();
}
-
+ if (DBG) {
+ for (String key : mAidCache.keySet()) {
+ Log.d(TAG, "aid cache entry" + key + " val:" + mAidCache.get(key).toString());
+ }
+ }
updateRoutingLocked(false);
}
@@ -1044,6 +1192,15 @@
}
}
+ public void onWalletRoleHolderChanged(String defaultWalletHolderPackageName, int userId) {
+ if (DBG) Log.d(TAG, "Default wallet holder changed for user:" + userId);
+ synchronized (mLock) {
+ mDefaultWalletHolderPackageName = defaultWalletHolderPackageName;
+ mUserIdDefaultWalletHolder = userId;
+ generateAidCacheLocked();
+ }
+ }
+
public ComponentName getPreferredService() {
if (mPreferredForegroundService != null) {
// return current foreground service
@@ -1053,6 +1210,40 @@
return mPreferredPaymentService;
}
}
+ public boolean isPreferredServicePackageNameForUser(String packageName, int userId) {
+ if (mPreferredForegroundService != null) {
+ if (mPreferredForegroundService.getPackageName().equals(packageName) &&
+ userId == mUserIdPreferredForegroundService) {
+ return true;
+ } else {
+ Log.i(TAG, "NfcService:" + packageName + "(" + userId
+ + ") is not equal to the foreground service "
+ + mPreferredForegroundService + "(" + mUserIdPreferredForegroundService +")" );
+ return false;
+ }
+ } else if(mWalletRoleObserver.isWalletRoleFeatureEnabled()) {
+ if (mDefaultWalletHolderPackageName != null &&
+ mDefaultWalletHolderPackageName.equals(packageName) &&
+ userId == mUserIdDefaultWalletHolder) {
+ return true;
+ } else {
+ Log.i(TAG, "NfcService:" + packageName + "(" + userId
+ + ") is not equal to the default wallet service "
+ + mDefaultWalletHolderPackageName + "(" + mUserIdDefaultWalletHolder +")" );
+ return false;
+ }
+ } else if (mPreferredPaymentService != null &&
+ userId == mUserIdPreferredPaymentService &&
+ mPreferredPaymentService.getPackageName().equals(packageName)) {
+ return true;
+ } else {
+ Log.i(TAG, "NfcService:" + packageName + "(" + userId
+ + ") is not equal to the default payment service "
+ + mPreferredPaymentService + "(" + mUserIdPreferredPaymentService +")" );
+ return false;
+ }
+ }
+
public void onNfcDisabled() {
synchronized (mLock) {
diff --git a/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java b/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java
index fd96f30..2aeab82 100644
--- a/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java
+++ b/src/com/android/nfc/cardemulation/RegisteredNfcFServicesCache.java
@@ -62,7 +62,8 @@
public class RegisteredNfcFServicesCache {
static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
static final String TAG = "RegisteredNfcFServicesCache";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
+ private static final boolean VDBG = false; // turn on for local testing.
final Context mContext;
final AtomicReference<BroadcastReceiver> mReceiver;
@@ -288,14 +289,14 @@
return;
}
ArrayList<NfcFServiceInfo> newServices = null;
+ ArrayList<NfcFServiceInfo> toBeAdded = new ArrayList<>();
+ ArrayList<NfcFServiceInfo> toBeRemoved = new ArrayList<>();
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
// Check update
ArrayList<NfcFServiceInfo> cachedServices =
new ArrayList<NfcFServiceInfo>(userServices.services.values());
- ArrayList<NfcFServiceInfo> toBeAdded = new ArrayList<NfcFServiceInfo>();
- ArrayList<NfcFServiceInfo> toBeRemoved = new ArrayList<NfcFServiceInfo>();
boolean matched = false;
for (NfcFServiceInfo validService : validServices) {
for (NfcFServiceInfo cachedService : cachedServices) {
@@ -332,11 +333,9 @@
// Update cache
for (NfcFServiceInfo service : toBeAdded) {
userServices.services.put(service.getComponent(), service);
- if (DBG) Log.d(TAG, "Added service: " + service.getComponent());
}
for (NfcFServiceInfo service : toBeRemoved) {
userServices.services.remove(service.getComponent());
- if (DBG) Log.d(TAG, "Removed service: " + service.getComponent());
}
// Apply dynamic System Code mappings
ArrayList<ComponentName> toBeRemovedDynamicSystemCode =
@@ -405,7 +404,16 @@
newServices = new ArrayList<NfcFServiceInfo>(userServices.services.values());
}
mCallback.onNfcFServicesUpdated(userId, Collections.unmodifiableList(newServices));
- if (DBG) dump(newServices);
+ if (VDBG) {
+ Log.i(TAG, "Services => ");
+ dump(newServices);
+ } else {
+ // dump only new services added or removed
+ Log.i(TAG, "New Services => ");
+ dump(toBeAdded);
+ Log.i(TAG, "Removed Services => ");
+ dump(toBeRemoved);
+ }
}
private void readDynamicSystemCodeNfcid2Locked() {
diff --git a/src/com/android/nfc/cardemulation/RegisteredServicesCache.java b/src/com/android/nfc/cardemulation/RegisteredServicesCache.java
index ce69f08..368127d 100644
--- a/src/com/android/nfc/cardemulation/RegisteredServicesCache.java
+++ b/src/com/android/nfc/cardemulation/RegisteredServicesCache.java
@@ -16,6 +16,8 @@
package com.android.nfc.cardemulation;
+import android.annotation.TargetApi;
+import android.annotation.FlaggedApi;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -36,15 +38,17 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.sysprop.NfcProperties;
+import android.text.TextUtils;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -53,8 +57,10 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -74,7 +80,11 @@
public class RegisteredServicesCache {
static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output";
static final String TAG = "RegisteredServicesCache";
- static final boolean DEBUG = NfcProperties.debug_enabled().orElse(false);
+ static final String AID_XML_PATH = "dynamic_aids.xml";
+ static final String OTHER_STATUS_PATH = "other_status.xml";
+ static final String PACKAGE_DATA = "package";
+ static final boolean DEBUG = NfcProperties.debug_enabled().orElse(true);
+ private static final boolean VDBG = false; // turn on for local testing.
final Context mContext;
final AtomicReference<BroadcastReceiver> mReceiver;
@@ -89,8 +99,10 @@
// mUserServices holds the card emulation services that are running for each user
final SparseArray<UserServices> mUserServices = new SparseArray<UserServices>();
final Callback mCallback;
- final AtomicFile mDynamicSettingsFile;
- final AtomicFile mOthersFile;
+ final SettingsFile mDynamicSettingsFile;
+ final SettingsFile mOthersFile;
+ final ServiceParser mServiceParser;
+
public interface Callback {
/**
* ServicesUpdated for specific userId.
@@ -103,7 +115,7 @@
public final int uid;
public final HashMap<String, AidGroup> aidGroups = new HashMap<>();
public String offHostSE;
- public boolean defaultToObserveMode = false;
+ public String shouldDefaultToObserveModeStr;
DynamicSettings(int uid) {
this.uid = uid;
@@ -120,7 +132,8 @@
}
};
- private static class UserServices {
+ @VisibleForTesting
+ static class UserServices {
/**
* All services that have registered
*/
@@ -132,6 +145,60 @@
new HashMap<>();
};
+ @VisibleForTesting
+ static class SettingsFile {
+ final AtomicFile mFile;
+ SettingsFile(Context context, String path) {
+ File dir = context.getFilesDir();
+ mFile = new AtomicFile(new File(dir, path));
+ }
+
+ boolean exists() {
+ return mFile.getBaseFile().exists();
+ }
+
+ InputStream openRead() throws FileNotFoundException {
+ return mFile.openRead();
+ }
+
+ void delete() {
+ mFile.delete();
+ }
+
+ FileOutputStream startWrite() throws IOException {
+ return mFile.startWrite();
+ }
+
+ void finishWrite(FileOutputStream fileOutputStream) {
+ mFile.finishWrite(fileOutputStream);
+ }
+
+ void failWrite(FileOutputStream fileOutputStream) {
+ mFile.failWrite(fileOutputStream);
+ }
+
+ File getBaseFile() {
+ return mFile.getBaseFile();
+ }
+ }
+
+ @VisibleForTesting
+ interface ServiceParser {
+ ApduServiceInfo parseApduService(PackageManager packageManager,
+ ResolveInfo resolveInfo,
+ boolean onHost) throws XmlPullParserException, IOException;
+ }
+
+ private static class RealServiceParser implements ServiceParser {
+
+ @Override
+ public ApduServiceInfo parseApduService(PackageManager packageManager,
+ ResolveInfo resolveInfo, boolean onHost)
+ throws XmlPullParserException, IOException {
+ return new ApduServiceInfo(packageManager, resolveInfo, onHost);
+ }
+ }
+
private UserServices findOrCreateUserLocked(int userId) {
UserServices services = mUserServices.get(userId);
if (services == null) {
@@ -150,8 +217,16 @@
}
public RegisteredServicesCache(Context context, Callback callback) {
+ this(context, callback, new SettingsFile(context, AID_XML_PATH),
+ new SettingsFile(context, OTHER_STATUS_PATH), new RealServiceParser());
+ }
+
+ @VisibleForTesting
+ RegisteredServicesCache(Context context, Callback callback, SettingsFile dynamicSettings,
+ SettingsFile otherSettings, ServiceParser serviceParser) {
mContext = context;
mCallback = callback;
+ mServiceParser = serviceParser;
refreshUserProfilesLocked();
@@ -160,7 +235,7 @@
public void onReceive(Context context, Intent intent) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
String action = intent.getAction();
- if (DEBUG) Log.d(TAG, "Intent action: " + action);
+ if (VDBG) Log.d(TAG, "Intent action: " + action);
if (RoutingOptionManager.getInstance().isRoutingTableOverrided()) {
if (DEBUG) Log.d(TAG, "Routing table overrided. Skip invalidateCache()");
@@ -169,7 +244,7 @@
if (uid != -1) {
boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) &&
(Intent.ACTION_PACKAGE_ADDED.equals(action) ||
- Intent.ACTION_PACKAGE_REMOVED.equals(action));
+ Intent.ACTION_PACKAGE_REMOVED.equals(action));
if (!replaced) {
int currentUser = ActivityManager.getCurrentUser();
if (currentUser == getProfileParentId(UserHandle.
@@ -199,7 +274,7 @@
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH);
intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
- intentFilter.addDataScheme("package");
+ intentFilter.addDataScheme(PACKAGE_DATA);
mContext.registerReceiverForAllUsers(mReceiver.get(), intentFilter, null, null);
// Register for events related to sdcard operations
@@ -208,9 +283,8 @@
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverForAllUsers(mReceiver.get(), sdFilter, null, null);
- File dataDir = mContext.getFilesDir();
- mDynamicSettingsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml"));
- mOthersFile = new AtomicFile(new File(dataDir, "other_status.xml"));
+ mDynamicSettingsFile = dynamicSettings;
+ mOthersFile = otherSettings;
}
void initialize() {
@@ -250,12 +324,18 @@
mUserHandles.removeAll(removeUserHandles);
}
- void dump(ArrayList<ApduServiceInfo> services) {
+ void dump(List<ApduServiceInfo> services) {
for (ApduServiceInfo service : services) {
if (DEBUG) Log.d(TAG, service.toString());
}
}
+ void dump(ArrayList<ComponentName> services) {
+ for (ComponentName service : services) {
+ if (DEBUG) Log.d(TAG, service.toString());
+ }
+ }
+
boolean containsServiceLocked(ArrayList<ApduServiceInfo> services, ComponentName serviceName) {
for (ApduServiceInfo service : services) {
if (service.getComponent().equals(serviceName)) return true;
@@ -314,7 +394,6 @@
new Intent(OffHostApduService.SERVICE_INTERFACE),
ResolveInfoFlags.of(PackageManager.GET_META_DATA), UserHandle.of(userId));
resolvedServices.addAll(resolvedOffHostServices);
-
for (ResolveInfo resolvedService : resolvedServices) {
try {
boolean onHost = !resolvedOffHostServices.contains(resolvedService);
@@ -341,7 +420,8 @@
android.Manifest.permission.BIND_NFC_SERVICE);
continue;
}
- ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost);
+ ApduServiceInfo service = mServiceParser.parseApduService(pm, resolvedService,
+ onHost);
if (service != null) {
validServices.add(service);
}
@@ -363,6 +443,8 @@
if (validServices == null) {
return;
}
+ ArrayList<ApduServiceInfo> toBeAdded = new ArrayList<>();
+ ArrayList<ApduServiceInfo> toBeRemoved = new ArrayList<>();
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
@@ -373,18 +455,17 @@
Map.Entry<ComponentName, ApduServiceInfo> entry =
(Map.Entry<ComponentName, ApduServiceInfo>) it.next();
if (!containsServiceLocked(validServices, entry.getKey())) {
- Log.d(TAG, "Service removed: " + entry.getKey());
+ toBeRemoved.add(entry.getValue());
it.remove();
}
}
for (ApduServiceInfo service : validServices) {
- if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() +
- " AIDs: " + service.getAids());
+ toBeAdded.add(service);
userServices.services.put(service.getComponent(), service);
}
// Apply dynamic settings mappings
- ArrayList<ComponentName> toBeRemoved = new ArrayList<ComponentName>();
+ ArrayList<ComponentName> toBeRemovedComponent = new ArrayList<ComponentName>();
for (Map.Entry<ComponentName, DynamicSettings> entry :
userServices.dynamicSettings.entrySet()) {
// Verify component / uid match
@@ -392,7 +473,7 @@
DynamicSettings dynamicSettings = entry.getValue();
ApduServiceInfo serviceInfo = userServices.services.get(component);
if (serviceInfo == null || (serviceInfo.getUid() != dynamicSettings.uid)) {
- toBeRemoved.add(component);
+ toBeRemovedComponent.add(component);
continue;
} else {
for (AidGroup group : dynamicSettings.aidGroups.values()) {
@@ -401,10 +482,15 @@
if (dynamicSettings.offHostSE != null) {
serviceInfo.setOffHostSecureElement(dynamicSettings.offHostSE);
}
+ if (dynamicSettings.shouldDefaultToObserveModeStr != null) {
+ serviceInfo.setShouldDefaultToObserveMode(
+ convertValueToBoolean(dynamicSettings.shouldDefaultToObserveModeStr,
+ false));
+ }
}
}
if (toBeRemoved.size() > 0) {
- for (ComponentName component : toBeRemoved) {
+ for (ComponentName component : toBeRemovedComponent) {
Log.d(TAG, "Removing dynamic AIDs registered by " + component);
userServices.dynamicSettings.remove(component);
}
@@ -419,11 +505,22 @@
mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices),
validateInstalled);
- dump(validServices);
+ if (VDBG) {
+ Log.i(TAG, "Services => ");
+ dump(validServices);
+ } else {
+ // dump only new services added or removed
+ Log.i(TAG, "New Services => ");
+ dump(toBeAdded);
+ Log.i(TAG, "Removed Services => ");
+ dump(toBeRemoved);
+ }
}
private void invalidateOther(int userId, List<ApduServiceInfo> validOtherServices) {
Log.d(TAG, "invalidate : " + userId);
+ ArrayList<ComponentName> toBeAdded = new ArrayList<>();
+ ArrayList<ComponentName> toBeRemoved = new ArrayList<>();
// remove services
synchronized (mLock) {
UserServices userServices = findOrCreateUserLocked(userId);
@@ -435,7 +532,7 @@
Map.Entry<ComponentName, OtherServiceStatus> entry = it.next();
if (!containsServiceLocked((ArrayList<ApduServiceInfo>) validOtherServices,
entry.getKey())) {
- Log.d(TAG, "Service removed: " + entry.getKey());
+ toBeRemoved.add(entry.getKey());
needToWrite = true;
it.remove();
}
@@ -452,8 +549,10 @@
isChecked = true;
for (ApduServiceInfo service : validOtherServices) {
- Log.d(TAG, "update valid otherService: " + service.getComponent()
- + " AIDs: " + service.getAids());
+ if (VDBG) {
+ Log.d(TAG, "update valid otherService: " + service.getComponent()
+ + " AIDs: " + service.getAids());
+ }
if (!service.hasCategory(CardEmulation.CATEGORY_OTHER)) {
Log.e(TAG, "service does not have other category");
continue;
@@ -463,11 +562,9 @@
OtherServiceStatus status = userServices.others.get(component);
if (status == null) {
- Log.d(TAG, "New other service");
+ toBeAdded.add(service.getComponent());
status = new OtherServiceStatus(service.getUid(), isChecked);
needToWrite = true;
- } else {
- Log.d(TAG, "Existed other service");
}
service.setCategoryOtherServiceEnabled(status.checked);
userServices.others.put(component, status);
@@ -477,11 +574,37 @@
writeOthersLocked();
}
}
+ if (VDBG) {
+ Log.i(TAG, "Other Services => ");
+ dump(validOtherServices);
+ } else {
+ // dump only new services added or removed
+ Log.i(TAG, "New Other Services => ");
+ dump(toBeAdded);
+ Log.i(TAG, "Removed Other Services => ");
+ dump(toBeRemoved);
+ }
}
+
+ private static final boolean convertValueToBoolean(CharSequence value, boolean defaultValue) {
+ boolean result = false;
+
+ if (TextUtils.isEmpty(value)) {
+ return defaultValue;
+ }
+
+ if (value.equals("1")
+ || value.equals("true")
+ || value.equals("TRUE"))
+ result = true;
+
+ return result;
+ }
+
private void readDynamicSettingsLocked() {
- FileInputStream fis = null;
+ InputStream fis = null;
try {
- if (!mDynamicSettingsFile.getBaseFile().exists()) {
+ if (!mDynamicSettingsFile.exists()) {
Log.d(TAG, "Dynamic AIDs file does not exist.");
return;
}
@@ -499,7 +622,7 @@
ComponentName currentComponent = null;
int currentUid = -1;
String currentOffHostSE = null;
- boolean defaultToObserveMode = false;
+ String shouldDefaultToObserveModeStr = null;
ArrayList<AidGroup> currentGroups = new ArrayList<AidGroup>();
while (eventType != XmlPullParser.END_DOCUMENT) {
tagName = parser.getName();
@@ -508,8 +631,8 @@
String compString = parser.getAttributeValue(null, "component");
String uidString = parser.getAttributeValue(null, "uid");
String offHostString = parser.getAttributeValue(null, "offHostSE");
- String defaultToObserveModeStr =
- parser.getAttributeValue(null, "defaultToObserveMode");
+ shouldDefaultToObserveModeStr =
+ parser.getAttributeValue(null, "shouldDefaultToObserveMode");
if (compString == null || uidString == null) {
Log.e(TAG, "Invalid service attributes");
} else {
@@ -518,9 +641,6 @@
currentComponent = ComponentName.unflattenFromString(compString);
currentOffHostSE = offHostString;
inService = true;
- defaultToObserveMode =
- XmlUtils.convertValueToBoolean(defaultToObserveModeStr,
- false);
} catch (NumberFormatException e) {
Log.e(TAG, "Could not parse service uid");
}
@@ -541,12 +661,13 @@
(currentGroups.size() > 0 || currentOffHostSE != null)) {
final int userId = UserHandle.
getUserHandleForUid(currentUid).getIdentifier();
+ Log.d(TAG, " ## user id - " + userId);
DynamicSettings dynSettings = new DynamicSettings(currentUid);
for (AidGroup group : currentGroups) {
dynSettings.aidGroups.put(group.getCategory(), group);
}
dynSettings.offHostSE = currentOffHostSE;
- dynSettings.defaultToObserveMode = defaultToObserveMode;
+ dynSettings.shouldDefaultToObserveModeStr = shouldDefaultToObserveModeStr;
UserServices services = findOrCreateUserLocked(userId);
services.dynamicSettings.put(currentComponent, dynSettings);
}
@@ -561,7 +682,7 @@
};
}
} catch (Exception e) {
- Log.e(TAG, "Could not parse dynamic AIDs file, trashing.");
+ Log.e(TAG, "Could not parse dynamic AIDs file, trashing.", e);
mDynamicSettingsFile.delete();
} finally {
if (fis != null) {
@@ -576,9 +697,9 @@
private void readOthersLocked() {
Log.d(TAG, "read others locked");
- FileInputStream fis = null;
+ InputStream fis = null;
try {
- if (!mOthersFile.getBaseFile().exists()) {
+ if (!mOthersFile.exists()) {
Log.d(TAG, "Dynamic AIDs file does not exist.");
return;
}
@@ -621,7 +742,8 @@
// See if we have a valid service
if (currentComponent != null && currentUid >= 0) {
Log.d(TAG, " end of service tag");
- final int userId = UserHandle.getUserId(currentUid);
+ final int userId =
+ UserHandle.getUserHandleForUid(currentUid).getIdentifier();
OtherServiceStatus status =
new OtherServiceStatus(currentUid, checked);
Log.d(TAG, " ## user id - " + userId);
@@ -637,7 +759,7 @@
}
}
} catch (Exception e) {
- Log.e(TAG, "Could not parse others AIDs file, trashing.");
+ Log.e(TAG, "Could not parse others AIDs file, trashing.", e);
mOthersFile.delete();
} finally {
if (fis != null) {
@@ -668,8 +790,10 @@
if(service.getValue().offHostSE != null) {
out.attribute(null, "offHostSE", service.getValue().offHostSE);
}
- out.attribute(null,"defaultToObserveMode",
- Boolean.toString(service.getValue().defaultToObserveMode));
+ if (service.getValue().shouldDefaultToObserveModeStr != null) {
+ out.attribute(null, "shouldDefaultToObserveMode",
+ service.getValue().shouldDefaultToObserveModeStr);
+ }
for (AidGroup group : service.getValue().aidGroups.values()) {
group.writeAsXml(out);
}
@@ -740,7 +864,7 @@
mOthersFile.finishWrite(fos);
return true;
} catch (Exception e) {
- Log.e(TAG, "Error writing dynamic AIDs", e);
+ Log.e(TAG, "Error writing other status", e);
if (fos != null) {
mOthersFile.failWrite(fos);
}
@@ -833,7 +957,7 @@
return true;
}
- public boolean setServiceObserveModeDefault(int userId, int uid,
+ public boolean setShouldDefaultToObserveModeForService(int userId, int uid,
ComponentName componentName, boolean enable) {
synchronized (mLock) {
UserServices services = findOrCreateUserLocked(userId);
@@ -851,17 +975,136 @@
Log.e(TAG, "UID mismatch.");
return false;
}
+ serviceInfo.setShouldDefaultToObserveMode(enable);
DynamicSettings dynSettings = services.dynamicSettings.get(componentName);
if (dynSettings == null) {
dynSettings = new DynamicSettings(uid);
dynSettings.offHostSE = null;
services.dynamicSettings.put(componentName, dynSettings);
}
- dynSettings.defaultToObserveMode = enable;
+ dynSettings.shouldDefaultToObserveModeStr = Boolean.toString(enable);
}
return true;
}
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopFilterForService(int userId, int uid,
+ ComponentName componentName, String pollingLoopFilter,
+ boolean autoTransact) {
+ ArrayList<ApduServiceInfo> newServices = null;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ // Check if we can find this service
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo == null) {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ return false;
+ }
+ if (serviceInfo.getUid() != uid) {
+ // This is probably a good indication something is wrong here.
+ // Either newer service installed with different uid (but then
+ // we should have known about it), or somebody calling us from
+ // a different uid.
+ Log.e(TAG, "UID mismatch.");
+ return false;
+ }
+ if (!serviceInfo.isOnHost() && !autoTransact) {
+ return false;
+ }
+ serviceInfo.addPollingLoopFilter(pollingLoopFilter, autoTransact);
+ newServices = new ArrayList<ApduServiceInfo>(services.services.values());
+ }
+ mCallback.onServicesUpdated(userId, newServices, true);
+ return true;
+ }
+
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean removePollingLoopFilterForService(int userId, int uid,
+ ComponentName componentName, String pollingLoopFilter) {
+ ArrayList<ApduServiceInfo> newServices = null;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ // Check if we can find this service
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo == null) {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ return false;
+ }
+ if (serviceInfo.getUid() != uid) {
+ // This is probably a good indication something is wrong here.
+ // Either newer service installed with different uid (but then
+ // we should have known about it), or somebody calling us from
+ // a different uid.
+ Log.e(TAG, "UID mismatch.");
+ return false;
+ }
+ serviceInfo.removePollingLoopFilter(pollingLoopFilter);
+ newServices = new ArrayList<ApduServiceInfo>(services.services.values());
+ }
+ mCallback.onServicesUpdated(userId, newServices, true);
+ return true;
+ }
+
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopPatternFilterForService(int userId, int uid,
+ ComponentName componentName, String pollingLoopPatternFilter,
+ boolean autoTransact) {
+ ArrayList<ApduServiceInfo> newServices = null;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ // Check if we can find this service
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo == null) {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ return false;
+ }
+ if (serviceInfo.getUid() != uid) {
+ // This is probably a good indication something is wrong here.
+ // Either newer service installed with different uid (but then
+ // we should have known about it), or somebody calling us from
+ // a different uid.
+ Log.e(TAG, "UID mismatch.");
+ return false;
+ }
+ if (!serviceInfo.isOnHost() && !autoTransact) {
+ return false;
+ }
+ serviceInfo.addPollingLoopPatternFilter(pollingLoopPatternFilter, autoTransact);
+ newServices = new ArrayList<ApduServiceInfo>(services.services.values());
+ }
+ mCallback.onServicesUpdated(userId, newServices, true);
+ return true;
+ }
+
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean removePollingLoopPatternFilterForService(int userId, int uid,
+ ComponentName componentName, String pollingLoopPatternFilter) {
+ ArrayList<ApduServiceInfo> newServices = null;
+ synchronized (mLock) {
+ UserServices services = findOrCreateUserLocked(userId);
+ // Check if we can find this service
+ ApduServiceInfo serviceInfo = getService(userId, componentName);
+ if (serviceInfo == null) {
+ Log.e(TAG, "Service " + componentName + " does not exist.");
+ return false;
+ }
+ if (serviceInfo.getUid() != uid) {
+ // This is probably a good indication something is wrong here.
+ // Either newer service installed with different uid (but then
+ // we should have known about it), or somebody calling us from
+ // a different uid.
+ Log.e(TAG, "UID mismatch.");
+ return false;
+ }
+ serviceInfo.removePollingLoopPatternFilter(pollingLoopPatternFilter);
+ newServices = new ArrayList<ApduServiceInfo>(services.services.values());
+ }
+ mCallback.onServicesUpdated(userId, newServices, true);
+ return true;
+ }
+
+
+
public boolean registerAidGroupForService(int userId, int uid,
ComponentName componentName, AidGroup aidGroup) {
ArrayList<ApduServiceInfo> newServices = null;
@@ -1008,10 +1251,14 @@
return success;
}
- boolean doesServiceDefaultToObserveMode(int userId, ComponentName service) {
+ boolean doesServiceShouldDefaultToObserveMode(int userId, ComponentName service) {
UserServices services = findOrCreateUserLocked(userId);
- DynamicSettings dynSettings = services.dynamicSettings.get(service);
- return dynSettings != null && dynSettings.defaultToObserveMode;
+ ApduServiceInfo serviceInfo = services.services.get(service);
+ if (serviceInfo == null) {
+ Log.d(TAG, "serviceInfo is null");
+ return false;
+ }
+ return serviceInfo.shouldDefaultToObserveMode();
}
private boolean updateOtherServiceStatus(int userId, ApduServiceInfo service, boolean checked) {
diff --git a/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java b/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java
index 78923f4..e4188ed 100644
--- a/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java
+++ b/src/com/android/nfc/cardemulation/RegisteredT3tIdentifiersCache.java
@@ -38,7 +38,7 @@
public class RegisteredT3tIdentifiersCache {
static final String TAG = "RegisteredT3tIdentifiersCache";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
// All NFC-F services that have registered
final Map<Integer, List<NfcFServiceInfo>> mUserNfcFServiceInfo =
diff --git a/src/com/android/nfc/cardemulation/SystemCodeRoutingManager.java b/src/com/android/nfc/cardemulation/SystemCodeRoutingManager.java
index f1b92bb..d530cf9 100644
--- a/src/com/android/nfc/cardemulation/SystemCodeRoutingManager.java
+++ b/src/com/android/nfc/cardemulation/SystemCodeRoutingManager.java
@@ -31,7 +31,7 @@
public class SystemCodeRoutingManager {
static final String TAG = "SystemCodeRoutingManager";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
final Object mLock = new Object();
diff --git a/src/com/android/nfc/cardemulation/WalletRoleObserver.java b/src/com/android/nfc/cardemulation/WalletRoleObserver.java
new file mode 100644
index 0000000..00c8fed
--- /dev/null
+++ b/src/com/android/nfc/cardemulation/WalletRoleObserver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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 com.android.nfc.cardemulation;
+
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.permission.flags.Flags;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+
+public class WalletRoleObserver {
+
+ public interface Callback {
+ void onWalletRoleHolderChanged(String holder, int userId);
+ }
+ private Context mContext;
+ private RoleManager mRoleManager;
+ @VisibleForTesting
+ final OnRoleHoldersChangedListener mOnRoleHoldersChangedListener;
+ private Callback mCallback;
+
+ public WalletRoleObserver(Context context, RoleManager roleManager,
+ Callback callback) {
+ this.mContext = context;
+ this.mRoleManager = roleManager;
+ this.mCallback = callback;
+ this.mOnRoleHoldersChangedListener = (roleName, user) -> {
+ if (!roleName.equals(RoleManager.ROLE_WALLET)) {
+ return;
+ }
+ List<String> roleHolders = roleManager.getRoleHolders(RoleManager.ROLE_WALLET);
+ String roleHolder = roleHolders.isEmpty() ? null : roleHolders.get(0);
+ callback.onWalletRoleHolderChanged(roleHolder, user.getIdentifier());
+ };
+ this.mRoleManager.addOnRoleHoldersChangedListenerAsUser(context.getMainExecutor(),
+ mOnRoleHoldersChangedListener, UserHandle.ALL);
+ }
+
+ public String getDefaultWalletRoleHolder(int userId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mRoleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ return null;
+ }
+ List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
+ UserHandle.of(userId));
+ return roleHolders.isEmpty() ? null : roleHolders.get(0);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ boolean isWalletRoleFeatureEnabled() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return Flags.walletRoleEnabled();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void onUserSwitched(int userId) {
+ String roleHolder = getDefaultWalletRoleHolder(userId);
+ mCallback.onWalletRoleHolderChanged(roleHolder, userId);
+ }
+}
diff --git a/src/com/android/nfc/cardemulation/util/StatsdUtils.java b/src/com/android/nfc/cardemulation/util/StatsdUtils.java
index cedd5cd..26a8b37 100644
--- a/src/com/android/nfc/cardemulation/util/StatsdUtils.java
+++ b/src/com/android/nfc/cardemulation/util/StatsdUtils.java
@@ -15,8 +15,14 @@
*/
package com.android.nfc.cardemulation.util;
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1;
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2;
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN;
+
import android.annotation.FlaggedApi;
import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.PollingFrame;
+import android.os.Bundle;
import android.os.SystemClock;
import android.sysprop.NfcProperties;
import android.util.Log;
@@ -24,9 +30,12 @@
import com.android.nfc.NfcStatsLog;
import com.android.nfc.flags.Flags;
+import java.util.HashMap;
+import java.util.Objects;
+
@FlaggedApi(Flags.FLAG_STATSD_CE_EVENTS_FLAG)
public class StatsdUtils {
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
private final String TAG = "StatsdUtils";
public static final String SE_NAME_HCE = "HCE";
@@ -77,6 +86,14 @@
/** Current transaction's uid to log in statsd */
private int mTransactionUid = -1;
+ private static final byte FRAME_HEADER_ECP = 0x6A;
+ private static final byte FRAME_ECP_V1 = 0x01;
+ private static final byte FRAME_ECP_V2 = 0x02;
+ private static final int FRAME_ECP_MIN_SIZE = 5;
+
+ private static final int NO_GAIN_INFORMATION = -1;
+ private int mLastGainLevel = NO_GAIN_INFORMATION;
+
/** Result constants for statsd usage */
static enum StatsdResult {
SUCCESS,
@@ -249,4 +266,108 @@
};
logCardEmulationEvent(statsdCategory);
}
+
+ public void logFieldChanged(boolean isOn, int fieldStrength) {
+ NfcStatsLog.write(NfcStatsLog.NFC_FIELD_CHANGED,
+ isOn ? NfcStatsLog.NFC_FIELD_CHANGED__FIELD_STATUS__FIELD_ON
+ : NfcStatsLog.NFC_FIELD_CHANGED__FIELD_STATUS__FIELD_OFF, fieldStrength);
+
+ if (!isOn) {
+ mLastGainLevel = NO_GAIN_INFORMATION;
+ }
+ }
+
+ public void logObserveModeStateChanged(boolean enabled, int triggerSource, int latency) {
+ NfcStatsLog.write(NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED,
+ enabled ? NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__STATE__OBSERVE_MODE_ENABLED
+ : NfcStatsLog.NFC_OBSERVE_MODE_STATE_CHANGED__STATE__OBSERVE_MODE_DISABLED,
+ triggerSource, latency);
+ }
+
+ private final HashMap<String, PollingFrameLog> pollingFrameMap = new HashMap<>();
+
+ public void tallyPollingFrame(String frameDataHex, PollingFrame frame) {
+ int type = frame.getType();
+
+ int gainLevel = frame.getVendorSpecificGain();
+ if (gainLevel != -1) {
+ if (mLastGainLevel != gainLevel) {
+ logFieldChanged(true, gainLevel);
+ mLastGainLevel = gainLevel;
+ }
+ }
+
+ if (type == PollingFrame.POLLING_LOOP_TYPE_UNKNOWN) {
+ byte[] data = frame.getData();
+
+ PollingFrameLog log = pollingFrameMap.getOrDefault(frameDataHex, null);
+
+ if (log == null) {
+ PollingFrameLog frameLog = new PollingFrameLog(data);
+
+ pollingFrameMap.put(frameDataHex, frameLog);
+ } else {
+ log.repeatCount++;
+ }
+ }
+ }
+
+ public void logPollingFrames() {
+ for (PollingFrameLog log : pollingFrameMap.values()) {
+ writeToStatsd(log);
+ }
+ pollingFrameMap.clear();
+ }
+
+ protected static int getFrameType(byte[] data) {
+ int frameType =
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN;
+
+ if (data != null && data.length >= FRAME_ECP_MIN_SIZE && data[0] == FRAME_HEADER_ECP) {
+ frameType = switch (data[1]) {
+ case FRAME_ECP_V1 ->
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1;
+ case FRAME_ECP_V2 ->
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2;
+ default -> frameType;
+ };
+ }
+ return frameType;
+ }
+
+ protected void writeToStatsd(PollingFrameLog frameLog) {
+ NfcStatsLog.write(NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED,
+ frameLog.frameType,
+ frameLog.repeatCount);
+ }
+
+ protected static class PollingFrameLog {
+ int repeatCount = 1;
+ final int frameType;
+
+ public PollingFrameLog(byte[] data) {
+ frameType = getFrameType(data);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PollingFrameLog)) return false;
+ PollingFrameLog that = (PollingFrameLog) o;
+ return repeatCount == that.repeatCount && frameType == that.frameType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(repeatCount, frameType);
+ }
+
+ @Override
+ public String toString() {
+ return "PollingFrameLog{" +
+ "repeatCount=" + repeatCount +
+ ", frameType=" + frameType +
+ '}';
+ }
+ }
}
diff --git a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
index 3540499..c41aec1 100644
--- a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
+++ b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java
@@ -55,7 +55,7 @@
*/
public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceListener {
static final String TAG = "BluetoothPeripheralHandover";
- static final boolean DBG = NfcProperties.debug_enabled().orElse(false);
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
static final String ACTION_ALLOW_CONNECT = "com.android.nfc.handover.action.ALLOW_CONNECT";
static final String ACTION_DENY_CONNECT = "com.android.nfc.handover.action.DENY_CONNECT";
diff --git a/src/com/android/nfc/handover/HandoverDataParser.java b/src/com/android/nfc/handover/HandoverDataParser.java
index 5853047..8363f75 100644
--- a/src/com/android/nfc/handover/HandoverDataParser.java
+++ b/src/com/android/nfc/handover/HandoverDataParser.java
@@ -42,7 +42,7 @@
public class HandoverDataParser {
private static final String TAG = "NfcHandover";
private static final boolean DBG =
- NfcProperties.debug_enabled().orElse(false);
+ NfcProperties.debug_enabled().orElse(true);
private static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob"
.getBytes(StandardCharsets.US_ASCII);
diff --git a/src/com/android/nfc/wlc/NfcCharging.java b/src/com/android/nfc/wlc/NfcCharging.java
new file mode 100644
index 0000000..8b063ca
--- /dev/null
+++ b/src/com/android/nfc/wlc/NfcCharging.java
@@ -0,0 +1,1083 @@
+/*
+ * Copyright (C) 2023 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.
+ *
+ * Provide extensions for the implementation of the Nfc Charging
+ */
+
+package com.android.nfc.wlc;
+
+import android.app.Activity;
+import android.content.Context;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.Bundle;
+import android.sysprop.NfcProperties;
+import android.util.Log;
+
+import com.android.nfc.DeviceHost;
+import com.android.nfc.DeviceHost.TagEndpoint;
+import com.android.nfc.NfcService;
+
+import java.math.*;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class NfcCharging {
+ static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
+ private static final String TAG = "NfcWlcChargingActivity";
+
+ static final String VERSION = "1.0.0";
+
+ private Context mContext;
+ public static final byte[] WLCCAP = {0x57, 0x4c, 0x43, 0x43, 0x41, 0x50};
+ public static final byte[] WLCCTL = {0x57, 0x4c, 0x43, 0x43, 0x54, 0x4C};
+ public static final byte[] WLCSTAI = {0x57, 0x4c, 0x43, 0x53, 0x54, 0x41, 0x49};
+ public static final byte[] USIWLC = {0x75, 0x73, 0x69, 0x3A, 0x77, 0x6C, 0x63};
+ public static final byte[] WLCPI = {0x57, 0x4c, 0x43, 0x49, 0x4e, 0x46};
+
+ public static final String BatteryLevel = "Battery Level";
+ public static final String ReceivePower = "Receive Power";
+ public static final String ReceiveVoltage = "Receive Voltage";
+ public static final String ReceiveCurrent = "Receive Current";
+ public static final String TemperatureBattery = "Temperature Battery";
+ public static final String TemperatureListener = "Temperature Listener";
+ public static final String VendorId = "Vendor Id";
+ public static final String State = "State";
+ public static final int DISCONNECTED = 0;
+ public static final int CONNECTED_NOT_CHARGING = 1;
+ public static final int CONNECTED_CHARGING = 2;
+
+ static final byte MODE_REQ_STATIC = 0;
+ static final byte MODE_REQ_NEGOTIATED = 1;
+ static final byte MODE_REQ_BATTERY_FULL = 2;
+
+ static final int MODE_NON_AUTONOMOUS_WLCP = 0;
+
+ int mWatchdogTimeout = 1;
+ int mUpdatedBatteryLevel = -1;
+ int mLastState = -1;
+
+ int WLCState = 0;
+
+ // WLCCAP
+ int WlcCap_ModeReq = 0;
+ int Nwt_max = 0;
+ int WlcCap_NegoWait = 0;
+ int WlcCap_RdConf = 0;
+ int TNdefRdWt = 0;
+
+ int WlcCap_NdefRdWt = 0;
+ int WlcCap_CapWt = 0;
+ int TCapWt = 0;
+ int WlcCap_NdefWrTo = 0;
+ int TNdefWrTo = 0;
+ int WlcCap_NdefWrWt = 0;
+ int TNdefWrWt = 0;
+
+ int mNwcc_retry = 0;
+ int mNretry = 0;
+
+ // WLCCTL
+ int WlcCtl_ErrorFlag = 0;
+ int WlcCtl_BatteryStatus = 0;
+ int mCnt = -1;
+ int WlcCtl_Cnt_new = 0;
+ int WlcCtl_WptReq = 0;
+ int WlcCtl_WptDuration = 0;
+ int TWptDuration = 0;
+ int WlcCtl_WptInfoReq = 0;
+ int WlcCtl_PowerAdjReq = 0;
+ int WlcCtl_BatteryLevel = 0xFF;
+ int WlcCtl_HoldOffWt = 0;
+ int THoldOffWt = 0;
+
+ int WlcCtl_ReceivePower = 0;
+ int WlcCtl_ReceiveVoltage = 0;
+ int WlcCtl_TemperatureBattery = 0;
+ int WlcCtl_TemperatureWlcl = 0;
+
+ // WLCINF
+ int Ptx = 100;
+
+ // state machine
+ private static final int STATE_2 = 0; // Read WLC_CAP
+ private static final int STATE_6 = 1; // Static WPT
+ private static final int STATE_8 = 2; // Handle NEGO_WAIT
+ private static final int STATE_11 = 3; // Write WLCP_INFO
+ private static final int STATE_12 = 4; // Read WLCL_CTL
+ private static final int STATE_16 = 5; // Read confirmation
+ private static final int STATE_17 = 6; // Check WPT requested
+ private static final int STATE_21 = 7; // Handle WPT
+ private static final int STATE_22 = 8; // Handle INFO_REQ
+ private static final int STATE_24 = 9; // Handle removal detection
+ private static final int STATE_21_1 = 10; // Handle WPT time completed
+ private static final int STATE_21_2 = 11; // Handle FOD detection/removal
+
+ private DeviceHost mNativeNfcManager;
+ NdefMessage mNdefMessage;
+ byte[] mNdefPayload;
+ byte[] mNdefPayload2;
+ byte[] mNdefType;
+ TagEndpoint TagHandler;
+
+ public boolean NfcChargingOnGoing = false;
+ public boolean NfcChargingMode = false;
+ public boolean WLCL_Presence = false;
+
+ public boolean mFirstOccurrence = true;
+
+ Map<String, Integer> WlcDeviceInfo = new HashMap<>();
+
+ private native boolean startWlcPowerTransfer(int power_adj_req, int wpt_time_int);
+
+ private native boolean enableWlc(int enable);
+
+ private PresenceCheckWatchdog mWatchdogWlc;
+
+ public NfcCharging(Context context, DeviceHost mDeviceHost) {
+ if (DBG) Log.d(TAG, "NfcCharging - Constructor");
+ mContext = context;
+ mNativeNfcManager = mDeviceHost;
+
+ resetInternalValues();
+
+ mNdefMessage = null;
+ mNdefPayload = null;
+ mNdefPayload2 = null;
+ }
+
+ private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+
+ public static String bytesToHex(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ for (int j = 0; j < bytes.length; j++) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+
+ public void resetInternalValues() {
+ if (DBG) Log.d(TAG, "resetInternalValues");
+ mCnt = -1;
+ mNretry = 0;
+ WlcCap_ModeReq = 0;
+ WlcCtl_BatteryLevel = -1;
+ WlcCtl_ReceivePower = -1;
+ WlcCtl_ReceiveVoltage = -1;
+ WlcCtl_TemperatureBattery = -1;
+ WlcCtl_TemperatureWlcl = -1;
+ mUpdatedBatteryLevel = -1;
+ mLastState = -1;
+ WlcDeviceInfo.put(BatteryLevel, -1);
+ WlcDeviceInfo.put(ReceivePower, -1);
+ WlcDeviceInfo.put(ReceiveVoltage, -1);
+ WlcDeviceInfo.put(ReceiveCurrent, -1);
+ WlcDeviceInfo.put(TemperatureBattery, -1);
+ WlcDeviceInfo.put(TemperatureListener, -1);
+ WlcDeviceInfo.put(VendorId, -1);
+ WlcDeviceInfo.put(State, -1);
+
+ WlcCtl_ErrorFlag = 0;
+ mFirstOccurrence = true;
+ }
+
+ DeviceHost.TagDisconnectedCallback callbackTagDisconnection =
+ new DeviceHost.TagDisconnectedCallback() {
+ @Override
+ public void onTagDisconnected(long handle) {
+ Log.d(TAG, "onTagDisconnected");
+ disconnectNfcCharging();
+ WLCState = STATE_2;
+ NfcChargingOnGoing = false;
+ if (WLCL_Presence == true) {
+ WLCL_Presence = false;
+ if (DBG) Log.d(TAG, "Nfc Charging Listener lost");
+ }
+ NfcService.getInstance().sendScreenMessageAfterNfcCharging();
+ }
+ };
+
+ public boolean startNfcCharging(TagEndpoint t) {
+ if (DBG) Log.d(TAG, "startNfcCharging " + VERSION);
+ boolean NfcChargingEnabled = false;
+
+ TagHandler = t;
+ NfcChargingEnabled = enableWlc(MODE_NON_AUTONOMOUS_WLCP);
+ if (DBG) Log.d(TAG, "NfcChargingEnabled is " + NfcChargingEnabled);
+
+ if (NfcChargingEnabled) {
+ WLCL_Presence = true;
+ WLCState = STATE_2;
+ startNfcChargingPresenceChecking(50);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public void stopNfcCharging() {
+ if (DBG) Log.d(TAG, "stopNfcCharging " + VERSION);
+
+ NfcChargingOnGoing = false;
+ resetInternalValues();
+
+ mLastState = DISCONNECTED;
+ WlcDeviceInfo.put(State, mLastState);
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ disconnectPresenceCheck();
+
+ NfcChargingMode = false;
+
+ // Restart the polling loop
+
+ TagHandler.disconnect();
+ // Disable discovery and restart polling loop only if not screen state change pending
+ if (!NfcService.getInstance().sendScreenMessageAfterNfcCharging()) {
+ if (DBG) Log.d(TAG, "No pending screen state change, stop Nfc charging presence check");
+ stopNfcChargingPresenceChecking();
+ }
+ }
+
+ public boolean checkWlcCapMsg(NdefMessage ndefMsg) {
+ if (DBG) Log.d(TAG, "checkWlcCapMsg: enter");
+ boolean status = true;
+ NdefRecord[] ndefRecords = null;
+ long mDeviceId = 0;
+ int mVendorId = 0;
+ Byte ControlByte = 0;
+ if (ndefMsg != null) {
+ mNdefMessage = ndefMsg;
+ try {
+ ndefRecords = mNdefMessage.getRecords();
+ if (ndefRecords != null && ndefRecords.length > 0) {
+ if (DBG)
+ Log.d(TAG, "checkWlcCapMsg: number of ndefRecords = " + ndefRecords.length);
+ mNdefType = ndefRecords[0].getType();
+
+ if (mNdefType != null) {
+ mNdefPayload = ndefRecords[0].getPayload();
+ if (mNdefPayload != null && mNdefType != null) {
+ if (!Arrays.equals(mNdefType, WLCCAP)) {
+ if (DBG) Log.d(TAG, "NdefType not WLC_CAP");
+ return (status = false);
+ }
+ if (DBG) Log.d(TAG, "mNdefType = " + bytesToHex(mNdefType));
+ } else {
+ return (status = false);
+ }
+ } else {
+ Log.e(TAG, "NdefType null");
+ return (status = false);
+ }
+ } else {
+ Log.e(TAG, "ndefRecords == null or ndefRecords.length = 0)");
+ return (status = false);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error in getRecords " + e);
+ NfcChargingOnGoing = false;
+ TagHandler.startPresenceChecking(125, callbackTagDisconnection);
+ }
+
+ if ((mNdefPayload[1] & 0xC0) == 0xC0) {
+ if (DBG) Log.d(TAG, "Wrong Mode Req");
+ return (status = false);
+ }
+
+ WlcCap_ModeReq = (mNdefPayload[1] >> 6) & 0x3;
+ Nwt_max = (mNdefPayload[1] >> 2) & 0xF;
+ WlcCap_NegoWait = (mNdefPayload[1] >> 1) & 0x1;
+ if (DBG) Log.d(TAG, "WlcCap_NegoWait = " + WlcCap_NegoWait);
+ if (DBG) Log.d(TAG, "Nwt_max = " + Nwt_max);
+ WlcCap_RdConf = mNdefPayload[1] & 0x1;
+
+ WlcCap_CapWt = (mNdefPayload[2] & 0x1F);
+ if (WlcCap_CapWt > 0x13) WlcCap_CapWt = 0x13;
+ TCapWt = (int) Math.pow(2, (WlcCap_CapWt + 3));
+ if (TCapWt < 250) TCapWt = 250;
+ if (DBG) Log.d(TAG, "TCapWt = " + TCapWt);
+ TNdefRdWt = (int) (mNdefPayload[3] & 0xFF) * 10;
+ if (mNdefPayload[3] == 0 || mNdefPayload[3] == (byte)0xFF) TNdefRdWt = 2540;
+ if (DBG) Log.d(TAG, "TNdefRdWt = " + TNdefRdWt);
+ WlcCap_NdefWrTo = mNdefPayload[4];
+ if (WlcCap_NdefWrTo == 0 || WlcCap_NdefWrTo > 4) WlcCap_NdefWrTo = 4;
+ TNdefWrTo = (int) Math.pow(2, (WlcCap_NdefWrTo + 5));
+ if (DBG) Log.d(TAG, "TNdefWrTo = " + TNdefWrTo);
+ TNdefWrWt = mNdefPayload[5];
+ if (TNdefWrWt > 0x0A) TNdefWrWt = 0x0A;
+ if (DBG) Log.d(TAG, "TNdefWrWt = " + TNdefWrWt);
+
+ Log.d(TAG, " " + ndefRecords.length + " NdefRecords");
+ if (ndefRecords != null && ndefRecords.length > 1) {
+ for (int i = 1; i < ndefRecords.length; i++) {
+ mNdefType = ndefRecords[i].getType();
+ if (DBG) Log.d(TAG, "mNdefType = " + bytesToHex(mNdefType));
+ mNdefPayload2 = ndefRecords[i].getPayload();
+ if (mNdefPayload2 != null && mNdefType != null) {
+ if (Arrays.equals(mNdefType, WLCSTAI)) {
+ checkWlcStaiMsg(mNdefPayload2);
+ } else if (Arrays.equals(mNdefType, USIWLC)) {
+ if (DBG)
+ Log.d(
+ TAG,
+ "mNdefPayload USIWLC = "
+ + bytesToHex(mNdefPayload2)
+ + " length = "
+ + mNdefPayload2.length);
+
+ if (mNdefPayload2.length > 8) {
+ mVendorId = (mNdefPayload2[8] << 8 | mNdefPayload2[7]) >> 4;
+ Log.d(TAG, "VendorId = " + Integer.toHexString(mVendorId));
+ WlcDeviceInfo.put(VendorId, mVendorId);
+ mDeviceId = (long) ((mNdefPayload2[7] & 0x0F)) << 48;
+ for (int j = 6; j > 0; j--) {
+ mDeviceId |= (long) (mNdefPayload2[j] & 0xFF) << ((j - 1) * 8);
+ }
+ if (DBG) Log.d(TAG, "DeviceId = " + Long.toHexString(mDeviceId));
+ }
+ }
+ }
+ }
+ }
+ NfcChargingOnGoing = true;
+ } else {
+ status = false;
+ }
+ if (WlcDeviceInfo.get(BatteryLevel) > (mUpdatedBatteryLevel + 5)) {
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ mUpdatedBatteryLevel = WlcDeviceInfo.get(BatteryLevel);
+ }
+ if (DBG) Log.d(TAG, "checkWlcCapMsg: exit, status = " + status);
+ return status;
+ }
+
+ public boolean checkWlcCtlMsg(NdefMessage mNdefMessage) {
+ if (DBG) Log.d(TAG, "checkWlcCtlMsg: enter");
+
+ boolean status = true;
+ NdefRecord[] ndefRecords = null;
+
+ if (mNdefMessage != null) {
+ if (DBG) Log.d(TAG, "ndefMessage non null");
+ try {
+ ndefRecords = mNdefMessage.getRecords();
+ if (ndefRecords != null && ndefRecords.length > 0) {
+ mNdefType = ndefRecords[0].getType();
+ mNdefPayload = ndefRecords[0].getPayload();
+ if (mNdefPayload != null && mNdefType != null) {
+ if (!Arrays.equals(mNdefType, NfcCharging.WLCCTL)) {
+ return (status = false);
+ }
+ if (DBG) Log.d(TAG, "mNdefType = " + bytesToHex(mNdefType));
+ } else {
+ return (status = false);
+ }
+ } else {
+ return (status = false);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error in getRecords " + e);
+ NfcChargingOnGoing = false;
+ TagHandler.startPresenceChecking(125, callbackTagDisconnection);
+ }
+ WlcCtl_ErrorFlag = (mNdefPayload[0] >> 7);
+ WlcCtl_BatteryStatus = (mNdefPayload[0] & 0x18) >> 3;
+ WlcCtl_Cnt_new = (mNdefPayload[0] & 0x7);
+ WlcCtl_WptReq = (mNdefPayload[1] & 0xC0) >> 6;
+ if (WlcCtl_WptReq > 1) WlcCtl_WptReq = 0;
+
+ WlcCtl_WptDuration = (mNdefPayload[1] & 0x3e) >> 1;
+ if (WlcCtl_WptDuration > 0x13) WlcCtl_WptReq = 0x13;
+ if (DBG) Log.d(TAG, "WlcCtl_WptDuration = " + WlcCtl_WptDuration);
+ TWptDuration = (int) Math.pow(2, (WlcCtl_WptDuration + 3));
+ WlcCtl_WptInfoReq = (mNdefPayload[1] & 0x1);
+ if (WlcCtl_WptReq == 0) WlcCtl_WptInfoReq = 0;
+
+ if ((mNdefPayload[2] <= 0x14) || (mNdefPayload[2] >= (byte)0xF6)) {
+ WlcCtl_PowerAdjReq = mNdefPayload[2];
+ } else {
+ WlcCtl_PowerAdjReq = 0;
+ }
+
+ if (DBG) Log.d(TAG, "checkWlcCtlMsg WlcCtl_PowerAdjReq = " + WlcCtl_PowerAdjReq);
+
+ if ((mNdefPayload[3] < 0x64) && (WlcCtl_BatteryStatus == 0x1)) {
+ WlcCtl_BatteryLevel = mNdefPayload[3];
+ WlcDeviceInfo.put(BatteryLevel, WlcCtl_BatteryLevel);
+ if (DBG) Log.d(TAG, "checkWlcCtlMsg WlcCtl_BatteryLevel = " + WlcCtl_BatteryLevel);
+ }
+
+ if (mNdefPayload[5] > 0xF) {
+ WlcCtl_HoldOffWt = 0xF;
+ } else {
+ WlcCtl_HoldOffWt = mNdefPayload[5];
+ }
+ THoldOffWt = (int) WlcCtl_HoldOffWt * 2;
+
+ if (DBG) Log.d(TAG, " " + ndefRecords.length + " NdefRecords");
+ if (ndefRecords != null && ndefRecords.length > 1) {
+ for (int i = 1; i < ndefRecords.length; i++) {
+ mNdefType = ndefRecords[i].getType();
+ if (DBG) Log.d(TAG, "mNdefType = " + bytesToHex(mNdefType));
+ mNdefPayload2 = ndefRecords[i].getPayload();
+ if (mNdefPayload2 != null && mNdefType != null) {
+ if (Arrays.equals(mNdefType, WLCSTAI)) {
+ checkWlcStaiMsg(mNdefPayload2);
+ }
+ }
+ }
+ }
+
+ } else {
+ status = false;
+ }
+
+ if (WlcDeviceInfo.get(BatteryLevel) > (mUpdatedBatteryLevel + 5)) {
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ mUpdatedBatteryLevel = WlcDeviceInfo.get(BatteryLevel);
+ }
+ if (DBG) Log.d(TAG, "checkWlcCtlMsg status = " + status);
+ return status;
+ }
+
+ public void checkWlcStaiMsg(byte[] mPayload) {
+ Byte ControlByte = 0;
+ if (DBG) Log.d(TAG, "mNdefPayload WLCSTAI = " + bytesToHex(mPayload));
+ ControlByte = mPayload[0];
+ int pos = 0;
+ if (((ControlByte & 0x01) == 0x01) && pos < mPayload.length) {
+ pos++;
+ WlcCtl_BatteryLevel = mPayload[pos];
+ WlcDeviceInfo.put(BatteryLevel, (int) mPayload[pos]);
+ if (DBG) Log.d(TAG, "WlcCtl_BatteryLevel = " + WlcDeviceInfo.get(BatteryLevel));
+ }
+ if (((ControlByte & 0x02) == 0x02) && pos < mPayload.length) {
+ pos++;
+ WlcCtl_ReceivePower = mPayload[pos];
+ WlcDeviceInfo.put(ReceivePower, (int) mPayload[pos]);
+ }
+ if (((ControlByte & 0x04) == 0x04) && pos < mPayload.length) {
+ pos++;
+ WlcCtl_ReceiveVoltage = mPayload[pos];
+ WlcDeviceInfo.put(ReceiveVoltage, (int) mPayload[pos]);
+ }
+ if (((ControlByte & 0x08) == 0x08) && pos < mPayload.length) {
+ pos++;
+ WlcCtl_TemperatureBattery = mPayload[pos];
+ WlcDeviceInfo.put(TemperatureBattery, (int) mPayload[pos]);
+ }
+ if (((ControlByte & 0x10) == 0x10) && pos < mPayload.length) {
+ pos++;
+ WlcCtl_TemperatureWlcl = mPayload[pos];
+ WlcDeviceInfo.put(TemperatureListener, (int) mPayload[pos]);
+ }
+ }
+
+ public void sendWLCPI(TagEndpoint tag, NdefMessage ndefMsg) {
+ NdefMessage WLCP_INFO =
+ constructWLCPI(
+ (byte) Ptx,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00);
+ if (tag.writeNdef(WLCP_INFO.toByteArray())) {
+ Log.d(TAG, "Write NDEF success");
+ } else {
+ Log.d(TAG, "Write NDEF Error");
+ }
+ }
+
+ public NdefMessage constructWLCPI(
+ byte ptx, byte power_class, byte tps, byte cps, byte nmsi, byte nmsd) {
+ byte[] WLCPI_payload = {ptx, power_class, tps, cps, nmsi, nmsd};
+
+ NdefRecord WLCP_INFO_RECORD =
+ new NdefRecord(NdefRecord.TNF_WELL_KNOWN, WLCPI, new byte[] {}, WLCPI_payload);
+
+ NdefMessage WLCP_INFO = new NdefMessage(WLCP_INFO_RECORD);
+
+ return WLCP_INFO;
+ }
+
+ public void sendEmptyNdef() {
+ NdefRecord WLCP_RD_CONF_RECORD = new NdefRecord(NdefRecord.TNF_EMPTY, null, null, null);
+
+ NdefMessage WLCP_RD_CONF = new NdefMessage(WLCP_RD_CONF_RECORD);
+ if (TagHandler.writeNdef(WLCP_RD_CONF.toByteArray())) {
+ Log.d(TAG, "Write NDEF success");
+ } else {
+ Log.d(TAG, "Write NDEF Error");
+ }
+ }
+
+ public synchronized void stopNfcChargingPresenceChecking() {
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.end(true);
+ }
+ }
+
+ public synchronized void startNfcChargingPresenceChecking(int presenceCheckDelay) {
+ // Once we start presence checking, we allow the upper layers
+ // to know the tag is in the field.
+ if (mWatchdogWlc != null) {
+ if (DBG) Log.d(TAG, "mWatchDog non null");
+ }
+ if (mWatchdogWlc == null) {
+ if (DBG) Log.d(TAG, "mWatchdogWlc about to start...");
+ mWatchdogWlc = new PresenceCheckWatchdog(presenceCheckDelay);
+ mWatchdogWlc.start();
+ }
+ }
+
+ class PresenceCheckWatchdog extends Thread {
+ private int watchdogTimeout;
+
+ private boolean isPresent = true;
+ private boolean isStopped = false;
+ private boolean isPaused = false;
+ private boolean doCheck = true;
+ private boolean isFull = false;
+
+ public PresenceCheckWatchdog(int presenceCheckDelay) {
+ watchdogTimeout = presenceCheckDelay;
+ }
+
+ public synchronized void pause() {
+ isPaused = true;
+ doCheck = false;
+ this.notifyAll();
+ if (DBG) Log.d(TAG, "pause - isPaused = " + isPaused);
+ }
+
+ public synchronized void setTimeout(int timeout) {
+ if (DBG) Log.d(TAG, "PresenceCheckWatchdog watchdogTimeout " + timeout);
+ watchdogTimeout = timeout;
+ }
+
+ public synchronized void full() {
+ isFull = true;
+ this.notifyAll();
+ }
+
+ public synchronized void lost() {
+ isPresent = false;
+ if (DBG) Log.d(TAG, "PresenceCheckWatchdog isPresent " + isPresent);
+ doCheck = false;
+ this.notifyAll();
+ }
+
+ public synchronized void doResume() {
+ isPaused = false;
+ // We don't want to resume presence checking immediately,
+ // but go through at least one more wait period.
+ doCheck = false;
+ this.notifyAll();
+ if (DBG) Log.d(TAG, "doResume - isPaused = " + isPaused);
+ }
+
+ public synchronized void end(boolean disableCallback) {
+ isStopped = true;
+ if (DBG) Log.d(TAG, "PresenceCheckWatchdog end isStopped = " + isStopped);
+ doCheck = false;
+ if (disableCallback) {
+ // tagDisconnectedCallback = null;
+ }
+ this.notifyAll();
+ }
+
+ @Override
+ public void run() {
+ synchronized (this) {
+ if (DBG) Log.d(TAG, "Starting WLC flow");
+ while (isPresent && !isStopped && !isFull) {
+ if (DBG)
+ Log.d(
+ TAG,
+ "isPresent= "
+ + isPresent
+ + " isStopped= "
+ + isStopped
+ + " isFull= "
+ + isFull);
+ try {
+ if (watchdogTimeout > 0) {
+ this.wait(watchdogTimeout);
+ }
+
+ watchdogTimeout = HandleWLCState();
+ if (DBG) Log.d(TAG, "Next watchdog timeout : " + watchdogTimeout);
+ } catch (InterruptedException e) {
+ // Activity detected, loop
+ if (DBG) Log.d(TAG, "Interrupted thread: " + WLCState);
+ }
+ }
+ }
+ synchronized (NfcCharging.this) {
+ isPresent = false;
+ NfcChargingOnGoing = false;
+ if (DBG)
+ Log.d(
+ TAG,
+ "WLC state machine interrupted, NfcChargingOnGoing is "
+ + NfcChargingOnGoing);
+ resetInternalValues();
+ }
+ mLastState = DISCONNECTED;
+ WlcDeviceInfo.put(State, mLastState);
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ disconnectPresenceCheck();
+ if (DBG) Log.d(TAG, "disconnectPresenceCheck done");
+
+ // Restart the polling loop
+ NfcChargingMode = false;
+ TagHandler.disconnect();
+ // Disable discovery and restart polling loop only if not screen state change pending
+ if (!NfcService.getInstance().sendScreenMessageAfterNfcCharging()) {
+ if (DBG)
+ Log.d(TAG, "No pending screen state change, stop Nfc charging presence check");
+ stopNfcChargingPresenceChecking();
+ }
+
+ if (DBG) Log.d(TAG, "Stopping background presence check");
+ }
+ }
+
+ public boolean disconnectPresenceCheck() {
+ boolean result = false;
+ PresenceCheckWatchdog watchdog;
+ if (DBG) Log.d(TAG, "disconnectPresenceCheck");
+ synchronized (this) {
+ watchdog = mWatchdogWlc;
+ }
+ if (watchdog != null) {
+ // Watchdog has already disconnected or will do it
+ watchdog.end(false);
+ synchronized (this) {
+ mWatchdogWlc = null;
+ }
+ }
+ result = true;
+ return result;
+ }
+
+ public int HandleWLCState() {
+ int wt = 1;
+ switch (WLCState) {
+ case STATE_2:
+ { // SM2
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_2 (" + convert_state_2_str(STATE_2) + ")");
+ if (mLastState != CONNECTED_CHARGING) {
+ mLastState = CONNECTED_CHARGING;
+ WlcDeviceInfo.put(State, mLastState);
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ }
+ if (TagHandler != null) {
+ if (!mFirstOccurrence) {
+ mNdefMessage = TagHandler.getNdef();
+ }
+
+ if (mNdefMessage != null) {
+ if (!mFirstOccurrence) {
+ if (checkWlcCapMsg(mNdefMessage) == false) {
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ WLCL_Presence = false;
+ Log.d(TAG, " WLC_CAP : Presence Check FAILED ");
+ break;
+ }
+ } else {
+ mFirstOccurrence = false;
+ }
+
+ if (WlcCap_ModeReq == MODE_REQ_BATTERY_FULL) {
+ mWatchdogWlc.full();
+ NfcChargingOnGoing = false;
+ if (DBG)
+ Log.d(
+ TAG,
+ "MODE_REQ is BATTERY_FULL, NfcChargingOnGoing is "
+ + NfcChargingOnGoing);
+ wt = TCapWt;
+
+ WLCState = STATE_24;
+ WlcDeviceInfo.put(BatteryLevel, 0x64);
+ mUpdatedBatteryLevel = WlcDeviceInfo.get(BatteryLevel);
+ WlcDeviceInfo.put(State, mLastState);
+ mLastState = CONNECTED_NOT_CHARGING;
+ NfcService.getInstance().onWlcData(WlcDeviceInfo);
+ if (DBG) Log.d(TAG, " Battery full");
+ break;
+
+ } else if (WlcCap_ModeReq == MODE_REQ_STATIC
+ || mNativeNfcManager.isMultiTag() == true) {
+ if (DBG) Log.d(TAG, " Static mode");
+ wt = 0; // TCapWt;
+
+ WLCState = STATE_6;
+ break;
+
+ } else {
+ if (DBG) Log.d(TAG, " Negotiated mode");
+ wt = 5;
+
+ WLCState = STATE_8;
+ break;
+ }
+ } else {
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ WLCL_Presence = false;
+ if (DBG) Log.d(TAG, " WLC_CAP: Presence Check FAILED");
+ }
+ }
+ break;
+ }
+
+ case STATE_6:
+ { // SM6
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_6 (" + convert_state_2_str(STATE_6) + ")");
+
+ WLCState = STATE_2;
+ wt = TCapWt + 5000;
+ startWlcPowerTransfer(WlcCtl_PowerAdjReq, WlcCap_CapWt);
+ break;
+ }
+
+ case STATE_8:
+ { // SM8
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_8 (" + convert_state_2_str(STATE_8) + ")");
+
+ if (WlcCap_NegoWait == 1) {
+ if (mNretry > Nwt_max) {
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ WLCL_Presence = false;
+ if (DBG) Log.d(TAG, " WLCCAP :too much retry, conclude procedure ");
+ WLCState = STATE_2;
+ wt = 1;
+ break;
+ } else {
+ mNretry += 1;
+ if (DBG) Log.d(TAG, "mNretry = " + mNretry);
+ wt = TCapWt;
+ WLCState = STATE_2;
+ break;
+ }
+ }
+ WLCState = STATE_11;
+ wt = 5;
+
+ break;
+ }
+
+ case STATE_11:
+ { // SM11
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_11 (" + convert_state_2_str(STATE_11) + ")");
+
+ sendWLCPI(TagHandler, null);
+ if (DBG) Log.d(TAG, "end writing WLCP_INFO");
+ wt = TNdefRdWt + 20;
+ WLCState = STATE_12;
+ break;
+ }
+
+ case STATE_12:
+ { // SM12-SM15
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_12 (" + convert_state_2_str(STATE_12) + ")");
+
+ if (TagHandler != null) {
+ mNdefMessage = TagHandler.getNdef();
+ if (mNdefMessage != null) {
+ if (checkWlcCtlMsg(mNdefMessage)) {
+ if (DBG)
+ Log.d(
+ TAG,
+ " WlcCtl_Cnt_new: "
+ + WlcCtl_Cnt_new
+ + "(mCnt +1)%8) = "
+ + ((mCnt + 1) % 7));
+
+ if (mCnt == -1) {
+ mCnt = WlcCtl_Cnt_new;
+ } else if (WlcCtl_Cnt_new == mCnt) {
+ if (mNwcc_retry < 3) {
+ wt = 30; // Twcc,retry
+ mNwcc_retry++;
+ break;
+ } else if (mNwcc_retry == 3) {
+ // go to error
+ if (DBG) Log.d(TAG, " WLCL_CTL : Max mNwcc_retry reached");
+ mNwcc_retry = 0;
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ break;
+ }
+ }
+ mNwcc_retry = 0;
+ mCnt = WlcCtl_Cnt_new;
+ if (WlcCap_RdConf == 1) {
+ WLCState = STATE_16;
+ wt = TNdefWrWt;
+ break;
+ }
+ wt = 1;
+ WLCState = STATE_17;
+ } else {
+ if (mNwcc_retry < 3) {
+ wt = 30; // Twcc,retry
+ mNwcc_retry++;
+ break;
+ } else if (mNwcc_retry == 3) {
+ // go to error
+ if (DBG)
+ Log.d(TAG, " WLCL_CTL not valid: Max mNwcc_retry reached");
+ mNwcc_retry = 0;
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ break;
+ }
+
+ WLCL_Presence = false;
+ if (DBG) Log.d(TAG, " WLCL_CTL : Presence Check Failed ");
+ }
+ } else {
+ // no more tag
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ WLCL_Presence = false;
+ if (DBG) Log.d(TAG, " WLCL_CTL : Presence Check Failed ");
+ }
+ } else {
+ // conclude - go to error
+ }
+ break;
+ }
+
+ case STATE_16:
+ { // SM16
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_16 (" + convert_state_2_str(STATE_16) + ")");
+
+ sendEmptyNdef();
+ WLCState = STATE_17;
+ wt = 1;
+ break;
+ }
+
+ case STATE_17:
+ { // SM17
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_17 (" + convert_state_2_str(STATE_17) + ")");
+
+ if (WlcCtl_WptReq == 0x0) {
+ // No Power transfer Required
+ if (DBG) Log.d(TAG, "No power transfer required");
+ // go to presence check SM24
+ WLCState = STATE_24;
+ wt = TWptDuration;
+ if (TWptDuration > 4000) {
+ TagHandler.startPresenceChecking(200, callbackTagDisconnection);
+ }
+ break;
+ }
+
+ // Adjust WPT
+ WLCState = STATE_21;
+ wt = 1 + THoldOffWt;
+ break;
+ }
+
+ case STATE_21:
+ { // SM21
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_21 (" + convert_state_2_str(STATE_21) + ")");
+
+ startWlcPowerTransfer(WlcCtl_PowerAdjReq, WlcCtl_WptDuration);
+ WLCState = STATE_22;
+ wt = TWptDuration + 5000;
+ break;
+ }
+
+ case STATE_22:
+ { // SM22
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_22 (" + convert_state_2_str(STATE_22) + ")");
+
+ if (WlcCtl_WptInfoReq == 1) {
+ WLCState = STATE_11;
+ break;
+ }
+ WLCState = STATE_12;
+ wt = 0;
+ break;
+ }
+
+ case STATE_24:
+ { // SM24
+ if (DBG)
+ Log.d(
+ TAG,
+ "HandleWLCState: STATE_24 (" + convert_state_2_str(STATE_24) + ")");
+
+ TagHandler.stopPresenceChecking();
+ WLCState = STATE_2;
+ NfcChargingOnGoing = false;
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ wt = 1;
+ break;
+ }
+ case STATE_21_1:
+ { // Stop WPT
+ if (DBG) Log.d(TAG, "HandleWLCState Time completed");
+ WLCState = STATE_22;
+ wt = 0;
+ break;
+ }
+ case STATE_21_2:
+ { // Stop WPT
+ if (DBG) Log.d(TAG, "HandleWLCState: STATE_21_2 (exit)");
+ WLCState = STATE_2;
+ NfcChargingOnGoing = false;
+
+ if (mWatchdogWlc != null) {
+ mWatchdogWlc.lost();
+ }
+ wt = 0;
+ break;
+ }
+ }
+
+ return wt;
+ }
+
+ public void disconnectNfcCharging() {
+ Log.d(TAG, "disconnectNfcCharging");
+ NfcChargingOnGoing = false;
+ NfcChargingMode = false;
+ resetInternalValues();
+ disconnectPresenceCheck();
+ if (TagHandler != null) {
+ TagHandler.disconnect();
+ }
+ }
+
+ public void onWlcStopped(int wpt_end_condition) {
+ Log.d(TAG, "onWlcStopped");
+
+ switch (wpt_end_condition) {
+ case 0x0:
+ // Time completed
+ mWatchdogWlc.setTimeout(0);
+ if (WlcCap_ModeReq == MODE_REQ_NEGOTIATED) {
+ WLCState = STATE_21_1;
+ } else {
+ WLCState = STATE_2;
+ }
+ mWatchdogWlc.interrupt();
+ if (DBG) Log.d(TAG, "Time completed");
+ break;
+
+ case 0x1:
+ // FOD detection or Removal
+ mWatchdogWlc.setTimeout(0);
+ if (WlcCap_ModeReq == MODE_REQ_NEGOTIATED) {
+ WLCState = STATE_21_2;
+ } else {
+ WLCState = STATE_2;
+ }
+ mWatchdogWlc.interrupt();
+ if (DBG) Log.d(TAG, "FOD detection or removal");
+ break;
+
+ case 0x3:
+ default:
+ // Error
+ mWatchdogWlc.setTimeout(0);
+ WLCState = STATE_21_2;
+ mWatchdogWlc.interrupt();
+ if (DBG) Log.d(TAG, "FOD error detection");
+ break;
+ }
+ }
+
+ public String convert_state_2_str(int state) {
+ switch (state) {
+ case STATE_2:
+ return "Read WLC_CAP";
+ case STATE_6:
+ return "Static WPT";
+ case STATE_8:
+ return "Handle NEGO_WAIT?";
+ case STATE_11:
+ return "Write WLCP_INFO";
+ case STATE_12:
+ return "Read WLCL_CTL";
+ case STATE_16:
+ return "Read confirmation?";
+ case STATE_17:
+ return "Check WPT requested?";
+ case STATE_21:
+ return "Handle WPT";
+ case STATE_22:
+ return "Handle INFO_REQ?";
+ case STATE_24:
+ return "Handle removal detection";
+ case STATE_21_1:
+ return "Handle WPT time completed";
+ case STATE_21_2:
+ return "Handle FOD detection/removal";
+
+ default:
+ return "Unknown";
+ }
+ }
+}
diff --git a/tests/instrumentation/Android.bp b/tests/instrumentation/Android.bp
index d592542..548d005 100644
--- a/tests/instrumentation/Android.bp
+++ b/tests/instrumentation/Android.bp
@@ -21,6 +21,8 @@
"androidx.test.rules",
"androidx.test.ext.junit",
"truth",
+ "androidx.test.espresso.core",
+ "androidx.test.espresso.intents-nodeps",
],
// Include all test java files.
diff --git a/tests/instrumentation/src/com/android/nfc/cardemulation/AppChooserActivityTest.java b/tests/instrumentation/src/com/android/nfc/cardemulation/AppChooserActivityTest.java
new file mode 100644
index 0000000..9e942ae
--- /dev/null
+++ b/tests/instrumentation/src/com/android/nfc/cardemulation/AppChooserActivityTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static android.app.Activity.RESULT_CANCELED;
+import static android.nfc.cardemulation.CardEmulation.CATEGORY_PAYMENT;
+import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_CATEGORY;
+import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_APDU_SERVICES;
+import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_FAILED_COMPONENT;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.widget.ListView;
+import android.view.Window;
+import android.view.View;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.espresso.UiController;
+import androidx.test.espresso.ViewAction;
+import androidx.test.espresso.matcher.ViewMatchers;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.nfc.R;
+
+import java.lang.IllegalStateException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.hamcrest.Matcher;
+
+import org.junit.Rule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppChooserActivityTest {
+ private static final String UNKNOWN_LABEL = "unknown";
+ private Context context;
+
+ @Before
+ public void setUp() throws Exception {
+ context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ }
+
+ @Test
+ public void testNoFailedComponentAndNoAlternatives() throws Exception {
+ ActivityScenario<AppChooserActivity> scenario
+ = ActivityScenario.launch(getIntent(/*isPayment = */ true,
+ /* withFailedComponent = */ false,
+ /* withServices = */ false));
+
+ assertThat(scenario.getState()).isEqualTo(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void testExistingFailedComponentAndNoAlternatives() throws Exception {
+ ActivityScenario<AppChooserActivity> scenario
+ = ActivityScenario.launch(getIntent(/*isPayment = */ true,
+ /* withFailedComponent = */ true,
+ /* withServices = */ false));
+
+ assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
+ String expectedText
+ = String.format(context.getString(R.string.transaction_failure), UNKNOWN_LABEL);
+ onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
+ scenario.onActivity(activity -> {
+ int flags = activity.getWindow().getAttributes().flags;
+ assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
+ assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
+ });
+ }
+
+ @Test
+ public void testNonPayment() throws Exception {
+ ActivityScenario<AppChooserActivity> scenario
+ = ActivityScenario.launch(getIntent(/*isPayment = */ false,
+ /* withFailedComponent = */ true,
+ /* withServices = */ true));
+
+ scenario.onActivity(activity -> {
+ ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
+ assertThat(listView.getDividerHeight()).isEqualTo(-1);
+ assertThat(listView.getPaddingEnd()).isEqualTo(0);
+ assertThat(listView.getPaddingLeft()).isEqualTo(0);
+ assertThat(listView.getPaddingRight()).isEqualTo(0);
+ assertThat(listView.getPaddingStart()).isEqualTo(0);
+ });
+ }
+
+ @Test
+ public void testExistingFailedComponentAndExistingAlternatives() throws Exception {
+ ActivityScenario<AppChooserActivity> scenario
+ = ActivityScenario.launch(getIntent(/*isPayment = */ true,
+ /* withFailedComponent = */ true,
+ /* withServices = */ true));
+
+ assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
+ String expectedText
+ = String.format(context.getString(R.string.could_not_use_app), UNKNOWN_LABEL);
+ onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
+ scenario.onActivity(activity -> {
+ int flags = activity.getWindow().getAttributes().flags;
+ assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
+ assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
+
+ ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
+ assertThat(listView.getDivider()).isNotNull();
+ assertThat((int) listView.getDividerHeight())
+ .isEqualTo((int) (context.getResources().getDisplayMetrics().density * 16));
+ assertThat(listView.getAdapter()).isNotNull();
+ });
+
+ // Test that onItemClick() does not throw an Exception
+ onView(withId(R.id.resolver_list)).perform(customClick());
+ }
+
+ @Test
+ public void testNoFailedComponentAndExistingAlternatives() throws Exception {
+ ActivityScenario<AppChooserActivity> scenario
+ = ActivityScenario.launch(getIntent(/*isPayment = */ true,
+ /* withFailedComponent = */ false,
+ /* withServices = */ true));
+
+ assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
+ String expectedText = context.getString(R.string.appchooser_description);
+ onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
+ scenario.onActivity(activity -> {
+ int flags = activity.getWindow().getAttributes().flags;
+ assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
+ assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
+
+ ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
+ assertThat(listView.getDivider()).isNotNull();
+ assertThat((int) listView.getDividerHeight())
+ .isEqualTo((int) (context.getResources().getDisplayMetrics().density * 16));
+ assertThat(listView.getAdapter()).isNotNull();
+ });
+
+ // Test that onItemClick() does not throw an Exception
+ onView(withId(R.id.resolver_list)).perform(customClick());
+ }
+
+ private Intent getIntent(boolean isPayment, boolean withFailedComponent, boolean withServices) {
+ Intent intent = new Intent(context, AppChooserActivity.class);
+ if (isPayment) {
+ intent.putExtra(EXTRA_CATEGORY, CATEGORY_PAYMENT);
+ } else {
+ intent.putExtra(EXTRA_CATEGORY, "");
+ }
+
+ ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
+ if (withServices) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = "com.nfc.test";
+ serviceInfo.name = "hce_service";
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ ApduServiceInfo service
+ = new ApduServiceInfo(resolveInfo,
+ /* onHost = */ false,
+ /* description = */ "",
+ /* staticAidGroups = */ new ArrayList<>(),
+ /* dynamicAidGroups = */ new ArrayList<>(),
+ /* requiresUnlock = */ false,
+ /* bannerResource = */ 0,
+ /* uid = */ 0,
+ /* settingsActivityName = */ "",
+ /* offHost = */ "",
+ /* staticOffHost = */ "");
+ services.add(service);
+ }
+ intent.putParcelableArrayListExtra(EXTRA_APDU_SERVICES, services);
+
+ if (withFailedComponent) {
+ ComponentName failedComponent
+ = new ComponentName("com.android.test.walletroleholder",
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+ intent.putExtra(EXTRA_FAILED_COMPONENT, failedComponent);
+ }
+ return intent;
+ }
+
+ // Bypasses the view.getGlobalVisibleRect() requirement on the default click() action
+ private ViewAction customClick() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return ViewMatchers.isEnabled();
+ }
+
+ @Override
+ public String getDescription() {
+ return "";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ view.performClick();
+ }
+ };
+ }
+}
diff --git a/tests/instrumentation/src/com/android/nfc/cardemulation/TapAgainDialogTest.java b/tests/instrumentation/src/com/android/nfc/cardemulation/TapAgainDialogTest.java
new file mode 100644
index 0000000..d69ee03
--- /dev/null
+++ b/tests/instrumentation/src/com/android/nfc/cardemulation/TapAgainDialogTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static com.android.nfc.cardemulation.TapAgainDialog.EXTRA_APDU_SERVICE;
+import static com.android.nfc.cardemulation.TapAgainDialog.EXTRA_CATEGORY;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.view.Window;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.nfc.R;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class TapAgainDialogTest {
+ private static final int ALERT_DIALOG_ID = 16909366;
+
+ @Test
+ public void testOnCreate() throws Exception {
+ ActivityScenario<TapAgainDialog> scenario = ActivityScenario.launch(getStartIntent());
+
+ assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
+
+ scenario.onActivity(activity -> {
+ int flags = activity.getWindow().getAttributes().flags;
+ assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
+ });
+ }
+
+ @Test
+ public void testOnClick() throws Exception {
+ ActivityScenario.launch(getStartIntent());
+
+ onView(withId(R.id.tap_again_toolbar)).perform(click());
+
+ onView(withId(ALERT_DIALOG_ID)).check(matches(isDisplayed()));
+ onView(withId(R.id.tap_again_toolbar)).check(matches(isDisplayed()));
+ onView(withId(R.id.tap_again_appicon)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testOnDestroy() throws Exception {
+ ActivityScenario<TapAgainDialog> scenario = ActivityScenario.launch(getStartIntent());
+ scenario.moveToState(Lifecycle.State.DESTROYED);
+
+ assertThat(scenario.getState()).isEqualTo(Lifecycle.State.DESTROYED);
+ }
+
+ @Test
+ public void testOnStop() throws Exception {
+ ActivityScenario<TapAgainDialog> scenario = ActivityScenario.launch(getStartIntent());
+
+ // activity's onPause() and onStop() methods are called
+ scenario.moveToState(Lifecycle.State.CREATED);
+
+ assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
+ }
+
+ private Intent getStartIntent() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent intent = new Intent(context, TapAgainDialog.class);
+ intent.putExtra(EXTRA_CATEGORY, "");
+
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = "com.nfc.test";
+ serviceInfo.name = "hce_service";
+ serviceInfo.applicationInfo = new ApplicationInfo();
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = serviceInfo;
+ ApduServiceInfo service
+ = new ApduServiceInfo(resolveInfo,
+ /* onHost = */ false,
+ /* description = */ "",
+ /* staticAidGroups = */ new ArrayList<>(),
+ /* dynamicAidGroups = */ new ArrayList<>(),
+ /* requiresUnlock = */ false,
+ /* bannerResource = */ 0,
+ /* uid = */ 0,
+ /* settingsActivityName = */ "",
+ /* offHost = */ "",
+ /* staticOffHost = */ "");
+ intent.putExtra(EXTRA_APDU_SERVICE, service);
+ return intent;
+ }
+}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 09cc17e..0c2efc9 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -26,13 +26,16 @@
"androidx.annotation_annotation",
"androidx.appcompat_appcompat",
"com.google.android.material_material",
+ "nfc-event-log-proto",
"nfc_flags_lib",
"flag-junit",
"platform-test-annotations",
+ "testables",
],
jni_libs: [
// Required for ExtendedMockito
+ "libnfc_nci_jni",
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
],
diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml
index f80ef8d..9a45b16 100644
--- a/tests/unit/AndroidManifest.xml
+++ b/tests/unit/AndroidManifest.xml
@@ -26,6 +26,11 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="com.android.nfc.cardemulation.AppChooserActivity"
+ android:theme="@style/Theme.AppCompat"
+ android:exported="false"
+ />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/unit/assets/dynamic_aids.xml b/tests/unit/assets/dynamic_aids.xml
new file mode 100644
index 0000000..9b3420a
--- /dev/null
+++ b/tests/unit/assets/dynamic_aids.xml
@@ -0,0 +1,28 @@
+<services>
+ <service component="com.android.test.walletroleholder/com.android.test.walletroleholder.OnHostApduService"
+ uid="1"
+ offHostSE="offhostse1"
+ shouldDefaultToObserveMode="true">
+ <aid-group category="payment">
+ <aid value="A000000004101011"/>
+ <aid value="A000000004101012"/>
+ <aid value="A000000004101013"/>
+ </aid-group>
+ </service>
+ <service component="com.android.test.nonpaymentnfc/com.android.test.nonpaymentnfc.NonPaymentApduService"
+ uid="1"
+ offHostSE="offhostse2"
+ shouldDefaultToObserveMode="false">
+ <aid-group category="other">
+ <aid value="F053414950454D"/>
+ </aid-group>
+ </service>
+ <service component="com.android.test.another/com.android.test.another.OffHostApduService"
+ uid="1"
+ offHostSE="offhostse3"
+ shouldDefaultToObserveMode="false">
+ <aid-group category="other">
+ <aid value="F053414950454D"/>
+ </aid-group>
+ </service>
+</services>
\ No newline at end of file
diff --git a/tests/unit/assets/other_status.xml b/tests/unit/assets/other_status.xml
new file mode 100644
index 0000000..58106f6
--- /dev/null
+++ b/tests/unit/assets/other_status.xml
@@ -0,0 +1,6 @@
+<services>
+ <service component="com.android.test.another/com.android.test.another.OffHostApduService"
+ uid="1"
+ checked="true">
+ </service>
+</services>
\ No newline at end of file
diff --git a/tests/unit/res/layout/activity_layout_test.xml b/tests/unit/res/layout/activity_layout_test.xml
new file mode 100644
index 0000000..d442e09
--- /dev/null
+++ b/tests/unit/res/layout/activity_layout_test.xml
@@ -0,0 +1,33 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_height="72dp"
+ android:layout_width="match_parent">
+
+ <ImageView android:id="@+id/appicon"
+ android:contentDescription="App Icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="24dp"
+ android:scaleType="fitCenter"
+ android:gravity="center_vertical"/>
+
+ <TextView android:id="@+id/applabel"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:layout_width="match_parent"
+ android:layout_height="32dp"
+ android:gravity="center_vertical|start"
+ android:minLines="2"
+ android:maxLines="2"
+ android:paddingStart="4dip"
+ android:paddingEnd="4dip" />
+ <ImageView android:id="@+id/banner"
+ android:contentDescription="Banner"
+ android:layout_height="32dp"
+ android:layout_width="32dp"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="24dp"
+ android:gravity="center" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/unit/src/com/android/nfc/NfcEventLogTest.java b/tests/unit/src/com/android/nfc/NfcEventLogTest.java
new file mode 100644
index 0000000..9ca3cb9
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/NfcEventLogTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 com.android.nfc;
+
+import static com.android.nfc.NfcEventLog.FORMATTER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.test.TestLooper;
+import android.util.AtomicFile;
+
+import com.android.nfc.proto.NfcEventProto;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.AdditionalMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import java.io.FileOutputStream;
+import java.time.LocalDateTime;
+import java.util.ArrayDeque;
+
+@RunWith(AndroidJUnit4.class)
+public class NfcEventLogTest {
+ @Mock Context mContext;
+ @Mock NfcInjector mNfcInjector;
+ @Mock AtomicFile mLogFile;
+ @Mock Resources mResources;
+ @Mock FileOutputStream mFileOutputStream;
+ TestLooper mLooper;
+ NfcEventLog mNfcEventLog;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mLooper = new TestLooper();
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getInteger(R.integer.max_event_log_num)).thenReturn(10);
+ when(mLogFile.startWrite()).thenReturn(mFileOutputStream);
+ mNfcEventLog = new NfcEventLog(mContext, mNfcInjector, mLooper.getLooper(), mLogFile);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testLogEvent() throws Exception {
+ NfcEventProto.EventType eventType =
+ NfcEventProto.EventType.newBuilder()
+ .setBootupState(NfcEventProto.NfcBootupState.newBuilder()
+ .setEnabled(true)
+ .build())
+ .build();
+ LocalDateTime localDateTime = LocalDateTime.MIN;
+ when(mNfcInjector.getLocalDateTime()).thenReturn(localDateTime);
+ mNfcEventLog.logEvent(eventType);
+ mLooper.dispatchAll();
+
+ NfcEventProto.Event expectedEvent = NfcEventProto.Event.newBuilder()
+ .setTimestamp(localDateTime.format(FORMATTER))
+ .setEventType(eventType)
+ .build();
+ NfcEventProto.EventList expectedEventList =
+ NfcEventProto.EventList.newBuilder().addEvents(expectedEvent).build();
+ verify(mFileOutputStream).write(AdditionalMatchers.aryEq(expectedEventList.toByteArray()));
+ }
+
+ @Test
+ public void testMultipleLogEvents() throws Exception {
+ NfcEventProto.EventType eventType =
+ NfcEventProto.EventType.newBuilder()
+ .setBootupState(NfcEventProto.NfcBootupState.newBuilder()
+ .setEnabled(true)
+ .build())
+ .build();
+ LocalDateTime localDateTime = LocalDateTime.MIN;
+ when(mNfcInjector.getLocalDateTime()).thenReturn(localDateTime);
+
+ // Log the event twice.
+ mNfcEventLog.logEvent(eventType);
+ mLooper.dispatchAll();
+
+ mNfcEventLog.logEvent(eventType);
+ mLooper.dispatchAll();
+
+ NfcEventProto.Event expectedEvent = NfcEventProto.Event.newBuilder()
+ .setTimestamp(localDateTime.format(FORMATTER))
+ .setEventType(eventType)
+ .build();
+ NfcEventProto.EventList expectedEventList =
+ NfcEventProto.EventList.newBuilder()
+ .addEvents(expectedEvent)
+ .addEvents(expectedEvent)
+ .build();
+ verify(mFileOutputStream).write(AdditionalMatchers.aryEq(expectedEventList.toByteArray()));
+ }
+
+ @Test
+ public void testReadEventsFromLogFile() throws Exception {
+ LocalDateTime localDateTime = LocalDateTime.MIN;
+ NfcEventProto.EventType eventType =
+ NfcEventProto.EventType.newBuilder()
+ .setBootupState(NfcEventProto.NfcBootupState.newBuilder()
+ .setEnabled(true)
+ .build())
+ .build();
+ NfcEventProto.Event event = NfcEventProto.Event.newBuilder()
+ .setTimestamp(localDateTime.format(FORMATTER))
+ .setEventType(eventType)
+ .build();
+ NfcEventProto.EventList eventList =
+ NfcEventProto.EventList.newBuilder()
+ .addEvents(event)
+ .addEvents(event)
+ .build();
+
+ when(mLogFile.readFully()).thenReturn(eventList.toByteArray());
+ // Recreate the instance to simulate reading the log file.
+ mNfcEventLog = new NfcEventLog(mContext, mNfcInjector, mLooper.getLooper(), mLogFile);
+ mLooper.dispatchAll();
+
+ ArrayDeque<NfcEventProto.Event> expectedEventList = new ArrayDeque<NfcEventProto.Event>();
+ expectedEventList.add(event);
+ expectedEventList.add(event);
+
+ ArrayDeque<NfcEventProto.Event> retrievedEventsList = mNfcEventLog.getEventsList();
+ assertThat(retrievedEventsList.toString()).isEqualTo(expectedEventList.toString());
+
+ }
+}
diff --git a/tests/unit/src/com/android/nfc/NfcServiceTest.java b/tests/unit/src/com/android/nfc/NfcServiceTest.java
new file mode 100644
index 0000000..af97fbb
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/NfcServiceTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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 com.android.nfc;
+
+import static com.android.nfc.NfcService.PREF_NFC_ON;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.Application;
+import android.app.KeyguardManager;
+import android.app.backup.BackupManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.nfc.NfcServiceManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.PowerManager;
+import android.os.UserManager;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+@RunWith(AndroidJUnit4.class)
+public final class NfcServiceTest {
+ private static final String PKG_NAME = "com.test";
+ @Mock Application mApplication;
+ @Mock NfcInjector mNfcInjector;
+ @Mock DeviceHost mDeviceHost;
+ @Mock NfcEventLog mNfcEventLog;
+ @Mock NfcDispatcher mNfcDispatcher;
+ @Mock NfcUnlockManager mNfcUnlockManager;
+ @Mock SharedPreferences mPreferences;
+ @Mock SharedPreferences.Editor mPreferencesEditor;
+ @Mock PowerManager mPowerManager;
+ @Mock PackageManager mPackageManager;
+ @Mock ScreenStateHelper mScreenStateHelper;
+ @Mock Resources mResources;
+ @Mock KeyguardManager mKeyguardManager;
+ @Mock UserManager mUserManager;
+ @Mock ActivityManager mActivityManager;
+ @Mock NfcServiceManager.ServiceRegisterer mNfcManagerRegisterer;
+ @Mock NfcDiagnostics mNfcDiagnostics;
+ @Mock DeviceConfigFacade mDeviceConfigFacade;
+ @Mock ContentResolver mContentResolver;
+ @Mock Bundle mUserRestrictions;
+ @Mock BackupManager mBackupManager;
+ @Captor ArgumentCaptor<DeviceHost.DeviceHostListener> mDeviceHostListener;
+ @Captor ArgumentCaptor<BroadcastReceiver> mGlobalReceiver;
+ TestLooper mLooper;
+ NfcService mNfcService;
+ private MockitoSession mStaticMockSession;
+
+ @Before
+ public void setUp() {
+ mLooper = new TestLooper();
+ MockitoAnnotations.initMocks(this);
+ AsyncTask.setDefaultExecutor(new HandlerExecutor(new Handler(mLooper.getLooper())));
+
+ when(mNfcInjector.getMainLooper()).thenReturn(mLooper.getLooper());
+ when(mNfcInjector.getNfcEventLog()).thenReturn(mNfcEventLog);
+ when(mNfcInjector.makeDeviceHost(any())).thenReturn(mDeviceHost);
+ when(mNfcInjector.getScreenStateHelper()).thenReturn(mScreenStateHelper);
+ when(mNfcInjector.getNfcDiagnostics()).thenReturn(mNfcDiagnostics);
+ when(mNfcInjector.getDeviceConfigFacade()).thenReturn(mDeviceConfigFacade);
+ when(mNfcInjector.getNfcManagerRegisterer()).thenReturn(mNfcManagerRegisterer);
+ when(mNfcInjector.getBackupManager()).thenReturn(mBackupManager);
+ when(mNfcInjector.getNfcDispatcher()).thenReturn(mNfcDispatcher);
+ when(mNfcInjector.getNfcUnlockManager()).thenReturn(mNfcUnlockManager);
+ when(mApplication.getSharedPreferences(anyString(), anyInt())).thenReturn(mPreferences);
+ when(mApplication.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+ when(mApplication.getSystemService(UserManager.class)).thenReturn(mUserManager);
+ when(mApplication.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+ when(mApplication.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ when(mApplication.getPackageManager()).thenReturn(mPackageManager);
+ when(mApplication.getResources()).thenReturn(mResources);
+ when(mApplication.createContextAsUser(any(), anyInt())).thenReturn(mApplication);
+ when(mApplication.getContentResolver()).thenReturn(mContentResolver);
+ when(mUserManager.getUserRestrictions()).thenReturn(mUserRestrictions);
+ when(mResources.getStringArray(R.array.nfc_allow_list)).thenReturn(new String[0]);
+ when(mPreferences.edit()).thenReturn(mPreferencesEditor);
+ when(mPowerManager.newWakeLock(anyInt(), anyString()))
+ .thenReturn(mock(PowerManager.WakeLock.class));
+ createNfcService();
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ private void createNfcService() {
+ mNfcService = new NfcService(mApplication, mNfcInjector);
+ mLooper.dispatchAll();
+ verify(mNfcInjector).makeDeviceHost(mDeviceHostListener.capture());
+ verify(mApplication).registerReceiverForAllUsers(
+ mGlobalReceiver.capture(),
+ argThat(intent -> intent.hasAction(Intent.ACTION_SCREEN_ON)), any(), any());
+ clearInvocations(mDeviceHost, mNfcInjector, mApplication);
+ }
+
+ private void enableAndVerify() throws Exception {
+ when(mDeviceHost.initialize()).thenReturn(true);
+ when(mPreferences.getBoolean(eq(PREF_NFC_ON), anyBoolean())).thenReturn(true);
+ mNfcService.mNfcAdapter.enable(PKG_NAME);
+ verify(mPreferencesEditor).putBoolean(PREF_NFC_ON, true);
+ mLooper.dispatchAll();
+ verify(mDeviceHost).initialize();
+ clearInvocations(mDeviceHost, mPreferencesEditor);
+ }
+
+ private void disableAndVerify() throws Exception {
+ when(mDeviceHost.deinitialize()).thenReturn(true);
+ when(mPreferences.getBoolean(eq(PREF_NFC_ON), anyBoolean())).thenReturn(false);
+ mNfcService.mNfcAdapter.disable(true, PKG_NAME);
+ verify(mPreferencesEditor).putBoolean(PREF_NFC_ON, false);
+ mLooper.dispatchAll();
+ verify(mDeviceHost).deinitialize();
+ verify(mNfcDispatcher).resetForegroundDispatch();
+ clearInvocations(mDeviceHost, mPreferencesEditor, mNfcDispatcher);
+ }
+
+ @Test
+ public void testEnable() throws Exception {
+ enableAndVerify();
+ }
+
+ @Test
+ public void testDisable() throws Exception {
+ enableAndVerify();
+ disableAndVerify();
+ }
+
+ @Test
+ public void testSimStateChange() throws Exception {
+ when(mResources.getBoolean(R.bool.restart_on_sim_change)).thenReturn(true);
+ createNfcService();
+
+ enableAndVerify();
+ Intent intent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
+ .putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_LOADED);
+ mGlobalReceiver.getValue().onReceive(mApplication, intent);
+ mLooper.dispatchAll();
+ verify(mDeviceHost).deinitialize();
+ verify(mDeviceHost).initialize();
+
+ enableAndVerify();
+ intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
+ .putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_ABSENT);
+ mGlobalReceiver.getValue().onReceive(mApplication, intent);
+ mLooper.dispatchAll();
+ verify(mDeviceHost).deinitialize();
+ verify(mDeviceHost).initialize();
+ }
+
+ @Test
+ public void testSimStateChangeWhenNfcIsDisabled() throws Exception {
+ when(mResources.getBoolean(R.bool.restart_on_sim_change)).thenReturn(true);
+ createNfcService();
+
+ Intent intent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
+ .putExtra(TelephonyManager.EXTRA_SIM_STATE, TelephonyManager.SIM_STATE_LOADED);
+ mGlobalReceiver.getValue().onReceive(mApplication, intent);
+ mLooper.dispatchAll();
+ verifyNoMoreInteractions(mDeviceHost);
+ }
+}
diff --git a/tests/unit/src/com/android/nfc/RegisteredAidCacheTest.java b/tests/unit/src/com/android/nfc/RegisteredAidCacheTest.java
index b4faa8d..c145430 100644
--- a/tests/unit/src/com/android/nfc/RegisteredAidCacheTest.java
+++ b/tests/unit/src/com/android/nfc/RegisteredAidCacheTest.java
@@ -28,6 +28,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
@@ -37,6 +38,7 @@
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.nfc.cardemulation.AidRoutingManager;
import com.android.nfc.cardemulation.RegisteredAidCache;
+import com.android.nfc.cardemulation.WalletRoleObserver;
@RunWith(AndroidJUnit4.class)
public class RegisteredAidCacheTest {
@@ -46,6 +48,8 @@
private MockitoSession mStaticMockSession;
private RegisteredAidCache mRegisteredAidCache;
private Context mockContext;
+ @Mock
+ private WalletRoleObserver mWalletRoleObserver;
@Before
public void setUp() throws Exception {
@@ -67,7 +71,8 @@
AidRoutingManager routingManager = mock(AidRoutingManager.class);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
- () -> mRegisteredAidCache = new RegisteredAidCache(mockContext, routingManager));
+ () -> mRegisteredAidCache = new RegisteredAidCache(
+ mockContext, mWalletRoleObserver, routingManager));
Assert.assertNotNull(mRegisteredAidCache);
}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/AidRoutingManagerTest.java b/tests/unit/src/com/android/nfc/cardemulation/AidRoutingManagerTest.java
new file mode 100644
index 0000000..30aa829
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/AidRoutingManagerTest.java
@@ -0,0 +1,630 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static com.android.nfc.cardemulation.AidRoutingManager.AID_MATCHING_EXACT_ONLY;
+import static com.android.nfc.cardemulation.AidRoutingManager.AID_MATCHING_EXACT_OR_PREFIX;
+import static com.android.nfc.cardemulation.AidRoutingManager.AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
+import static com.android.nfc.cardemulation.AidRoutingManager.AID_MATCHING_PREFIX_ONLY;
+import static com.android.nfc.cardemulation.AidRoutingManager.ROUTE_HOST;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.NfcService;
+import com.android.nfc.NfcStatsLog;
+import com.android.nfc.cardemulation.AidRoutingManager.AidEntry;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class AidRoutingManagerTest {
+
+ private AidRoutingManager manager;
+ private MockitoSession mStaticMockSession;
+
+ @Mock
+ private RoutingOptionManager mRoutingOptionManager;
+ @Mock
+ private NfcService mNfcService;
+ @Mock
+ private PrintWriter mPw;
+
+ @Captor
+ private ArgumentCaptor<String> unroutedAidsCaptor;
+ @Captor
+ private ArgumentCaptor<String> routedAidsCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> routeCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> aidTypeCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> powerCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> codeCaptor;
+ @Captor
+ private ArgumentCaptor<Integer> arg1Captor;
+ @Captor
+ private ArgumentCaptor<Integer> arg2Captor;
+ @Captor
+ private ArgumentCaptor<Integer> arg3Captor;
+
+ private static final int DEFAULT_ROUTE = 0;
+ private static final int OVERRIDE_DEFAULT_ROUTE = 10;
+ private static final int DEFAULT_OFFHOST_ROUTE = 20;
+ private static final int OVERRIDE_ISODEP_ROUTE = 30;
+ private static final byte[] OFFHOST_ROUTE_UICC = new byte[] {5, 6, 7, 8};
+ private static final byte[] OFFHOST_ROUTE_ESE = new byte[] {1, 2, 3, 4};
+ private static final int FIRST_AID_ENTRY_POWER = 1;
+ private static final int FIRST_AID_ENTRY_AID_INFO = 2;
+ private static final int SECOND_AID_ENTRY_POWER = 3;
+ private static final int SECOND_AID_ENTRY_AID_INFO = 4;
+ private static final int THIRD_AID_ENTRY_POWER = 5;
+ private static final int THIRD_AID_ENTRY_AID_INFO = 6;
+ private static final int FOURTH_AID_ENTRY_POWER = 7;
+ private static final int FOURTH_AID_ENTRY_AID_INFO = 8;
+
+ @Before
+ public void setUp() {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(RoutingOptionManager.class)
+ .mockStatic(NfcService.class)
+ .mockStatic(NfcStatsLog.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ when(RoutingOptionManager.getInstance()).thenReturn(mRoutingOptionManager);
+ when(NfcService.getInstance()).thenReturn(mNfcService);
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructor() {
+ manager = new AidRoutingManager();
+ }
+
+ @Test
+ public void testSupportsAidPrefixRouting_ReturnsTrue() {
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_OR_PREFIX);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.supportsAidPrefixRouting();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void testSupportsAidPrefixRouting_ReturnsFalse() {
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_ONLY);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.supportsAidPrefixRouting();
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void testSupportsAidSubsetRouting_ReturnsTrue() {
+ when(mRoutingOptionManager.getAidMatchingSupport())
+ .thenReturn(AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.supportsAidSubsetRouting();
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void testSupportsAidSubsetRouting_ReturnsFalse() {
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_OR_PREFIX);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.supportsAidSubsetRouting();
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void testCalculateAidRouteSizeWithEmptyCache() {
+ manager = new AidRoutingManager();
+
+ int result = manager.calculateAidRouteSize(new HashMap<String, AidEntry>());
+
+ assertThat(result).isEqualTo(0);
+ }
+
+ @Test
+ public void testCalculateAidRouteSizeWithNonEmptyCache() {
+ String firstAidEntry = "0000000000";
+ String secondAidEntry = "000000000000000";
+ HashMap<String, AidEntry> cache = new HashMap<>();
+ cache.put(firstAidEntry + "*", null);
+ cache.put(secondAidEntry, null);
+ manager = new AidRoutingManager();
+
+ int result = manager.calculateAidRouteSize(cache);
+
+ int expected = (firstAidEntry.length() / 2) + 4 + (secondAidEntry.length() / 2) + 4;
+ assertThat(result).isEqualTo(expected);
+ }
+
+ @Test
+ public void testConfigureRoutingWithEmptyMap_ReturnsFalse() {
+ manager = new AidRoutingManager();
+
+ boolean result = manager.configureRouting(/* aidMap = */ new HashMap<String, AidEntry>(),
+ /* force = */ false);
+
+ assertThat(result).isFalse();
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is overridden to the value OVERRIDE_DEFAULT_ROUTE.
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are non-null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_PREFIX_ONLY
+ * (4) mDefaultIsoDepRoute is equal to ROUTE_HOST (so that the default route is registered)
+ * (5) NCI Version 2 is used.
+ *
+ * Ultimately, the contents of aidMap should be committed.
+ */
+ @Test
+ public void testConfigureRoutingTestCase1_CommitsCache() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(true);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getOverrideDefaultRoute()).thenReturn(OVERRIDE_DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(OFFHOST_ROUTE_UICC);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(OFFHOST_ROUTE_ESE);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_PREFIX_ONLY);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(ROUTE_HOST);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_2_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+ manager.mRouteForAid.put("first*", 0);
+ manager.mRouteForAid.put("second#", 0);
+ manager.mRouteForAid.put("third", 0);
+
+ boolean result = manager.configureRouting(getAidMap(), /* force = */ false);
+
+ assertThat(result).isTrue();
+ verify(mNfcService, times(4)).unrouteAids(unroutedAidsCaptor.capture());
+ assertThat(unroutedAidsCaptor.getAllValues().contains("first")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("second#")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("third")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("")).isTrue();
+ verify(mNfcService, times(3)).routeAids(routedAidsCaptor.capture(),
+ routeCaptor.capture(),
+ aidTypeCaptor.capture(),
+ powerCaptor.capture());
+ assertThat(routedAidsCaptor.getAllValues().get(0)).isEqualTo("");
+ assertThat(routedAidsCaptor.getAllValues().get(1)).isEqualTo("firstAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(2)).isEqualTo("fourthAidEntry");
+ assertThat(routeCaptor.getAllValues().get(0)).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(1)).isEqualTo(OFFHOST_ROUTE_ESE[1]);
+ assertThat(routeCaptor.getAllValues().get(2)).isEqualTo(OFFHOST_ROUTE_UICC[0]);
+ assertThat(aidTypeCaptor.getAllValues().get(0))
+ .isEqualTo(RegisteredAidCache.AID_ROUTE_QUAL_PREFIX);
+ assertThat(aidTypeCaptor.getAllValues().get(1)).isEqualTo(FIRST_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(2)).isEqualTo(FOURTH_AID_ENTRY_AID_INFO);
+ assertThat(powerCaptor.getAllValues().get(0)).isEqualTo(RegisteredAidCache.POWER_STATE_ALL);
+ assertThat(powerCaptor.getAllValues().get(1)).isEqualTo(FIRST_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(2)).isEqualTo(FOURTH_AID_ENTRY_POWER);
+ verify(mNfcService).commitRouting();
+ assertThat(manager.mDefaultRoute).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(3);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(3);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(3);
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is unmodified (DEFAULT_ROUTE).
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are non-null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_PREFIX_ONLY
+ * (4) mDefaultIsoDepRoute is equal to ROUTE_HOST (so that the default route is registered)
+ * (5) NCI Version 1 is used.
+ *
+ * Ultimately, nothing is committed and an error message is written to NfcStatsLog.
+ */
+ @Test
+ public void testConfigureRoutingTestCase2_WritesError() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(false);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getDefaultRoute()).thenReturn(DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(OFFHOST_ROUTE_UICC);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(OFFHOST_ROUTE_ESE);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_PREFIX_ONLY);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(ROUTE_HOST);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_1_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.configureRouting(getAidMap(), /* force = */ false);
+
+ assertThat(result).isTrue();
+ verify(mNfcService, never()).unrouteAids(anyString());
+ verify(mNfcService, never()).routeAids(anyString(), anyInt(), anyInt(), anyInt());
+ verify(mNfcService, never()).commitRouting();
+ ExtendedMockito.verify(() -> NfcStatsLog.write(codeCaptor.capture(),
+ arg1Captor.capture(),
+ arg2Captor.capture(),
+ arg3Captor.capture()));
+ assertThat(codeCaptor.getValue()).isEqualTo(NfcStatsLog.NFC_ERROR_OCCURRED);
+ assertThat(arg1Captor.getValue()).isEqualTo(NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__AID_OVERFLOW);
+ assertThat(arg2Captor.getValue()).isEqualTo(0);
+ assertThat(arg3Captor.getValue()).isEqualTo(0);
+ assertThat(manager.mDefaultRoute).isEqualTo(OFFHOST_ROUTE_ESE[1]);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(3);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(3);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(3);
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is unmodified (DEFAULT_ROUTE).
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are non-null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_ONLY
+ * (4) mDefaultIsoDepRoute is equal to OVERRIDE_ISODEP_ROUTE (so that the default route is not
+ * registered)
+ * (5) NCI Version 2 is used.
+ *
+ * Ultimately, the routing table is not updated and no other action is taken.
+ */
+ @Test
+ public void testConfigureRoutingTestCase3_DoNothing() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(false);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getDefaultRoute()).thenReturn(DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(OFFHOST_ROUTE_UICC);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(OFFHOST_ROUTE_ESE);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_ONLY);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(OVERRIDE_ISODEP_ROUTE);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_1_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+ manager.mRouteForAid.put("first*", 0);
+ manager.mRouteForAid.put("second#", 0);
+ manager.mRouteForAid.put("third", 0);
+ // Create a HashMap with only one AidEntry
+ HashMap<String, AidEntry> aidMap = new HashMap<>();
+ AidEntry aidEntry = manager.new AidEntry();
+ aidEntry.isOnHost = false;
+ aidEntry.offHostSE = "eSE2";
+ aidEntry.power = 1;
+ aidEntry.aidInfo = 2;
+ aidMap.put("firstAidEntry*", aidEntry);
+
+ boolean result = manager.configureRouting(aidMap, /* force = */ false);
+
+ assertThat(result).isTrue();
+
+ verify(mNfcService, times(3)).unrouteAids(unroutedAidsCaptor.capture());
+ assertThat(unroutedAidsCaptor.getAllValues().contains("first*")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("second#")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("third")).isTrue();
+ ExtendedMockito.verify(() ->
+ NfcStatsLog.write(anyInt(), anyInt(), anyInt(), anyInt()), times(0));
+ assertThat(manager.mDefaultRoute).isEqualTo(DEFAULT_ROUTE);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(1);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(1);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(1);
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is unmodified (DEFAULT_ROUTE).
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are non-null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_ONLY
+ * (4) mDefaultIsoDepRoute is equal to OVERRIDE_ISODEP_ROUTE (so that the default route is not
+ * registered)
+ * (5) NCI Version 2 is used.
+ *
+ * This case is identical to Test Case 3, with the exception of the value of the force variable,
+ * which causes the cache to be committed.
+ */
+ @Test
+ public void testConfigureRoutingTestCase4_CommitsCache() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(false);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getDefaultRoute()).thenReturn(DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(OFFHOST_ROUTE_UICC);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(OFFHOST_ROUTE_ESE);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_ONLY);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(OVERRIDE_ISODEP_ROUTE);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_1_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+ manager.mRouteForAid.put("first*", 0);
+ manager.mRouteForAid.put("second#", 0);
+ manager.mRouteForAid.put("third", 0);
+ // Create a HashMap with only one AidEntry
+ HashMap<String, AidEntry> aidMap = new HashMap<>();
+ AidEntry aidEntry = manager.new AidEntry();
+ aidEntry.isOnHost = false;
+ aidEntry.offHostSE = "eSE2";
+ aidEntry.power = 1;
+ aidEntry.aidInfo = 2;
+ aidMap.put("firstAidEntry*", aidEntry);
+
+ boolean result = manager.configureRouting(aidMap, /* force = */ true);
+
+ assertThat(result).isTrue();
+ verify(mNfcService).commitRouting();
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is overridden to the value OVERRIDE_DEFAULT_ROUTE.
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_EXACT_OR_PREFIX
+ * (4) mDefaultIsoDepRoute is equal to ROUTE_HOST (so that the default route is registered)
+ * (5) NCI Version 2 is used.
+ *
+ * Ultimately, the contents of aidMap should be committed.
+ */
+ @Test
+ public void testConfigureRoutingTestCase5_CommitsCache() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(true);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getOverrideDefaultRoute()).thenReturn(OVERRIDE_DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(null);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(null);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_EXACT_OR_PREFIX);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(ROUTE_HOST);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_2_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+ manager.mRouteForAid.put("first*", 0);
+ manager.mRouteForAid.put("second#", 0);
+ manager.mRouteForAid.put("third", 0);
+
+ boolean result = manager.configureRouting(getAidMap(), /* force = */ false);
+
+ assertThat(result).isTrue();
+ verify(mNfcService, times(4)).unrouteAids(unroutedAidsCaptor.capture());
+ assertThat(unroutedAidsCaptor.getAllValues().contains("first")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("second#")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("third")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("")).isTrue();
+ verify(mNfcService, times(4)).routeAids(routedAidsCaptor.capture(),
+ routeCaptor.capture(),
+ aidTypeCaptor.capture(),
+ powerCaptor.capture());
+ assertThat(routedAidsCaptor.getAllValues().get(0)).isEqualTo("");
+ assertThat(routedAidsCaptor.getAllValues().get(1)).isEqualTo("fourthAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(2)).isEqualTo("thirdAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(3)).isEqualTo("firstAidEntry");
+ assertThat(routeCaptor.getAllValues().get(0)).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(1)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(2)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(3)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(aidTypeCaptor.getAllValues().get(0))
+ .isEqualTo(RegisteredAidCache.AID_ROUTE_QUAL_PREFIX);
+ assertThat(aidTypeCaptor.getAllValues().get(1)).isEqualTo(FOURTH_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(2)).isEqualTo(THIRD_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(3)).isEqualTo(FIRST_AID_ENTRY_AID_INFO);
+ assertThat(powerCaptor.getAllValues().get(0)).isEqualTo(RegisteredAidCache.POWER_STATE_ALL);
+ assertThat(powerCaptor.getAllValues().get(1)).isEqualTo(FOURTH_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(2)).isEqualTo(THIRD_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(3)).isEqualTo(FIRST_AID_ENTRY_POWER);
+ verify(mNfcService).commitRouting();
+ assertThat(manager.mDefaultRoute).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(4);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(4);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is overridden to the value OVERRIDE_DEFAULT_ROUTE.
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX
+ * (4) mDefaultIsoDepRoute is equal to ROUTE_HOST (so that the default route is registered)
+ * (5) NCI Version 2 is used.
+ *
+ * Ultimately, the contents of aidMap should be committed.
+ */
+ @Test
+ public void testConfigureRoutingTestCase6_CommitsCache() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(true);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getOverrideDefaultRoute()).thenReturn(OVERRIDE_DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(null);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(null);
+ when(mRoutingOptionManager.getAidMatchingSupport())
+ .thenReturn(AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(ROUTE_HOST);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_2_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(0);
+ manager = new AidRoutingManager();
+ manager.mRouteForAid.put("first*", 0);
+ manager.mRouteForAid.put("second#", 0);
+ manager.mRouteForAid.put("third", 0);
+
+ boolean result = manager.configureRouting(getAidMap(), /* force = */ false);
+
+ assertThat(result).isTrue();
+ verify(mNfcService, times(4)).unrouteAids(unroutedAidsCaptor.capture());
+ assertThat(unroutedAidsCaptor.getAllValues().contains("first")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("second")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("third")).isTrue();
+ assertThat(unroutedAidsCaptor.getAllValues().contains("")).isTrue();
+ verify(mNfcService, times(5)).routeAids(routedAidsCaptor.capture(),
+ routeCaptor.capture(),
+ aidTypeCaptor.capture(),
+ powerCaptor.capture());
+ assertThat(routedAidsCaptor.getAllValues().get(0)).isEqualTo("");
+ assertThat(routedAidsCaptor.getAllValues().get(1)).isEqualTo("secondAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(2)).isEqualTo("fourthAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(3)).isEqualTo("thirdAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(4)).isEqualTo("firstAidEntry");
+ assertThat(routeCaptor.getAllValues().get(0)).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(1)).isEqualTo(DEFAULT_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(2)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(3)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(4)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(aidTypeCaptor.getAllValues().get(0))
+ .isEqualTo(RegisteredAidCache.AID_ROUTE_QUAL_PREFIX);
+ assertThat(aidTypeCaptor.getAllValues().get(1)).isEqualTo(SECOND_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(2)).isEqualTo(FOURTH_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(3)).isEqualTo(THIRD_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(4)).isEqualTo(FIRST_AID_ENTRY_AID_INFO);
+ assertThat(powerCaptor.getAllValues().get(0)).isEqualTo(RegisteredAidCache.POWER_STATE_ALL);
+ assertThat(powerCaptor.getAllValues().get(1)).isEqualTo(SECOND_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(2)).isEqualTo(FOURTH_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(3)).isEqualTo(THIRD_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(4)).isEqualTo(FIRST_AID_ENTRY_POWER);
+ verify(mNfcService).commitRouting();
+ assertThat(manager.mDefaultRoute).isEqualTo(OVERRIDE_DEFAULT_ROUTE);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(4);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(4);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(2);
+ }
+
+ /**
+ * Tests the case wherein:
+ * (1) The default route (mDefaultRoute) is unmodified (DEFAULT_ROUTE).
+ * (2) Both mOffHostRouteUicc and mOffHostRouteEse are null.
+ * (3) mAidMatchingSupport is equal to AID_MATCHING_PREFIX_ONLY
+ * (4) mDefaultIsoDepRoute is equal to ROUTE_HOST (so that the default route is registered)
+ * (5) NCI Version 1 is used.
+ *
+ * Ultimately, due to the value of mAidRoutingTableSize, the contents of the cache are committed.
+ */
+ @Test
+ public void testConfigureRoutingTestCase7_CommitsCache() {
+ when(mRoutingOptionManager.isRoutingTableOverrided()).thenReturn(false);
+ when(mRoutingOptionManager.getDefaultOffHostRoute()).thenReturn(DEFAULT_OFFHOST_ROUTE);
+ when(mRoutingOptionManager.getDefaultRoute()).thenReturn(DEFAULT_ROUTE);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(null);
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(null);
+ when(mRoutingOptionManager.getAidMatchingSupport()).thenReturn(AID_MATCHING_PREFIX_ONLY);
+ when(mRoutingOptionManager.getDefaultIsoDepRoute()).thenReturn(ROUTE_HOST);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_1_0);
+ when(mNfcService.getAidRoutingTableSize()).thenReturn(100);
+ manager = new AidRoutingManager();
+
+ boolean result = manager.configureRouting(getAidMap(), /* force = */ false);
+
+ assertThat(result).isTrue();
+ verify(mNfcService, never()).unrouteAids(anyString());
+ verify(mNfcService, times(3)).routeAids(routedAidsCaptor.capture(),
+ routeCaptor.capture(),
+ aidTypeCaptor.capture(),
+ powerCaptor.capture());
+ assertThat(routedAidsCaptor.getAllValues().get(0)).isEqualTo("fourthAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(1)).isEqualTo("firstAidEntry");
+ assertThat(routedAidsCaptor.getAllValues().get(2)).isEqualTo("thirdAidEntry");
+ assertThat(routeCaptor.getAllValues().get(0)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(1)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(routeCaptor.getAllValues().get(2)).isEqualTo(DEFAULT_OFFHOST_ROUTE);
+ assertThat(aidTypeCaptor.getAllValues().get(0)).isEqualTo(FOURTH_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(1)).isEqualTo(FIRST_AID_ENTRY_AID_INFO);
+ assertThat(aidTypeCaptor.getAllValues().get(2)).isEqualTo(THIRD_AID_ENTRY_AID_INFO);
+ assertThat(powerCaptor.getAllValues().get(0)).isEqualTo(FOURTH_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(1)).isEqualTo(FIRST_AID_ENTRY_POWER);
+ assertThat(powerCaptor.getAllValues().get(2)).isEqualTo(THIRD_AID_ENTRY_POWER);
+ verify(mNfcService).commitRouting();
+ assertThat(manager.mDefaultRoute).isEqualTo(DEFAULT_ROUTE);
+ assertThat(manager.mRouteForAid.size()).isEqualTo(4);
+ assertThat(manager.mPowerForAid.size()).isEqualTo(4);
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void testOnNfccRoutingTableCleared() {
+ manager = new AidRoutingManager();
+ manager.mAidRoutingTable.put(0, new HashSet<String>());
+ manager.mRouteForAid.put("", 0);
+ manager.mPowerForAid.put("", 0);
+
+ manager.onNfccRoutingTableCleared();
+
+ assertThat(manager.mAidRoutingTable.size()).isEqualTo(0);
+ assertThat(manager.mRouteForAid.isEmpty()).isTrue();
+ assertThat(manager.mPowerForAid.isEmpty()).isTrue();
+ }
+
+ @Test
+ public void testDump() {
+ manager = new AidRoutingManager();
+ HashSet<String> routingTableSet = new HashSet<>();
+ routingTableSet.add("");
+ manager.mAidRoutingTable.put(0, routingTableSet);
+
+ manager.dump(/* fd = */ null, mPw, /* args = */ null);
+
+ verify(mPw, times(4)).println(anyString());
+ }
+
+ private HashMap<String, AidEntry> getAidMap() {
+ HashMap<String, AidEntry> aidMap = new HashMap<>();
+ AidEntry firstAidEntry = manager.new AidEntry();
+ firstAidEntry.isOnHost = false;
+ firstAidEntry.offHostSE = "eSE2";
+ firstAidEntry.power = FIRST_AID_ENTRY_POWER;
+ firstAidEntry.aidInfo = FIRST_AID_ENTRY_AID_INFO;
+ aidMap.put("firstAidEntry*", firstAidEntry);
+
+ AidEntry secondAidEntry = manager.new AidEntry();
+ secondAidEntry.isOnHost = true;
+ secondAidEntry.power = SECOND_AID_ENTRY_POWER;
+ secondAidEntry.aidInfo = SECOND_AID_ENTRY_AID_INFO;
+ aidMap.put("secondAidEntry#", secondAidEntry);
+
+ AidEntry thirdAidEntry = manager.new AidEntry();
+ thirdAidEntry.isOnHost = false;
+ thirdAidEntry.offHostSE = "invalid SE";
+ thirdAidEntry.power = THIRD_AID_ENTRY_POWER;
+ thirdAidEntry.aidInfo = THIRD_AID_ENTRY_AID_INFO;
+ aidMap.put("thirdAidEntry", thirdAidEntry);
+
+ AidEntry fourthAidEntry = manager.new AidEntry();
+ fourthAidEntry.isOnHost = false;
+ fourthAidEntry.offHostSE = "SIM1";
+ fourthAidEntry.power = FOURTH_AID_ENTRY_POWER;
+ fourthAidEntry.aidInfo = FOURTH_AID_ENTRY_AID_INFO;
+ aidMap.put("fourthAidEntry", fourthAidEntry);
+
+ return aidMap;
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/nfc/cardemulation/CardEmulationManagerTest.java b/tests/unit/src/com/android/nfc/cardemulation/CardEmulationManagerTest.java
new file mode 100644
index 0000000..0fbb036
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/CardEmulationManagerTest.java
@@ -0,0 +1,1956 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.AidGroup;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.NfcFServiceInfo;
+import android.nfc.cardemulation.PollingFrame;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.ForegroundUtils;
+import com.android.nfc.NfcPermissions;
+import com.android.nfc.NfcService;
+import com.android.nfc.R;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.List;
+
+public class CardEmulationManagerTest {
+
+ private static final int USER_ID = 0;
+ private static final UserHandle USER_HANDLE = UserHandle.of(USER_ID);
+ private static final byte[] TEST_DATA_1 = new byte[] {(byte) 0xd2};
+ private static final byte[] TEST_DATA_2 = new byte[] {(byte) 0xd3};
+ private static final byte[] PROPER_SKIP_DATA_NDF1_HEADER = new byte[]
+ {0x00, (byte) 0xa4, 0x04, 0x00, (byte)0x07, (byte) 0xd2, 0x76, 0x00, 0x00,
+ (byte) 0x85, 0x01, 0x00};
+ private static final byte[] PROPER_SKIP_DATA_NDF2_HEADER = new byte[]
+ {0x00, (byte) 0xa4, 0x04, 0x00, (byte)0x07, (byte) 0xd2, 0x76, 0x00, 0x00,
+ (byte) 0x85, 0x01, 0x01};
+ private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
+ private static final List<PollingFrame> POLLING_LOOP_FRAMES = List.of();
+ private static final List<ApduServiceInfo> UPDATED_SERVICES = List.of();
+ private static final List<NfcFServiceInfo> UPDATED_NFC_SERVICES = List.of();
+ private static final ComponentName WALLET_PAYMENT_SERVICE
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+ private static final String PAYMENT_AID_1 = "A000000004101012";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private ForegroundUtils mForegroundUtils;
+ @Mock
+ private WalletRoleObserver mWalletRoleObserver;
+ @Mock
+ private RegisteredAidCache mRegisteredAidCache;
+ @Mock
+ private RegisteredT3tIdentifiersCache mRegisteredT3tIdentifiersCache;
+ @Mock
+ private HostEmulationManager mHostEmulationManager;
+ @Mock
+ private HostNfcFEmulationManager mHostNfcFEmulationManager;
+ @Mock
+ private RegisteredServicesCache mRegisteredServicesCache;
+ @Mock
+ private RegisteredNfcFServicesCache mRegisteredNfcFServicesCache;
+ @Mock
+ private PreferredServices mPreferredServices;
+ @Mock
+ private EnabledNfcFServices mEnabledNfcFServices;
+ @Mock
+ private RoutingOptionManager mRoutingOptionManager;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private NfcService mNfcService;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private NfcAdapter mNfcAdapter;
+ @Captor
+ private ArgumentCaptor<List<PollingFrame>> mPollingLoopFrameCaptor;
+ @Captor
+ private ArgumentCaptor<byte[]> mDataCaptor;
+ @Captor
+ private ArgumentCaptor<List<ApduServiceInfo>> mServiceListCaptor;
+ @Captor
+ private ArgumentCaptor<List<NfcFServiceInfo>> mNfcServiceListCaptor;
+ private MockitoSession mStaticMockSession;
+ private CardEmulationManager mCardEmulationManager;
+ @Before
+ public void setUp() {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(ActivityManager.class)
+ .mockStatic(NfcPermissions.class)
+ .mockStatic(android.nfc.Flags.class)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(NfcService.class)
+ .mockStatic(Binder.class)
+ .mockStatic(UserHandle.class)
+ .mockStatic(NfcAdapter.class)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ when(NfcAdapter.getDefaultAdapter(mContext)).thenReturn(mNfcAdapter);
+ when(NfcService.getInstance()).thenReturn(mNfcService);
+ when(ActivityManager.getCurrentUser()).thenReturn(USER_ID);
+ when(UserHandle.getUserHandleForUid(anyInt())).thenReturn(USER_HANDLE);
+ when(mContext.createContextAsUser(
+ any(), anyInt())).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(eq(UserManager.class))).thenReturn(mUserManager);
+ mCardEmulationManager = createInstanceWithMockParams();
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructor() {
+ assertConstructorMethodCalls();
+ }
+
+ private void assertConstructorMethodCalls() {
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mWalletRoleObserver).isWalletRoleFeatureEnabled();
+ verify(mWalletRoleObserver).getDefaultWalletRoleHolder(eq(USER_ID));
+ verify(mPreferredServices).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ }
+
+ @Test
+ public void testGetters() {
+ Assert.assertNotNull(mCardEmulationManager.getNfcCardEmulationInterface());
+ Assert.assertNotNull(mCardEmulationManager.getNfcFCardEmulationInterface());
+ }
+
+ @Test
+ public void testPollingLoopDetected() {
+ mCardEmulationManager.onPollingLoopDetected(POLLING_LOOP_FRAMES);
+
+ verify(mHostEmulationManager).onPollingLoopDetected(mPollingLoopFrameCaptor.capture());
+ Assert.assertEquals(mPollingLoopFrameCaptor.getValue(), POLLING_LOOP_FRAMES);
+ }
+
+ @Test
+ public void testOnHostCardEmulationActivated_technologyApdu() {
+ mCardEmulationManager.onHostCardEmulationActivated(CardEmulationManager.NFC_HCE_APDU);
+
+ verify(mPowerManager).userActivity(anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ eq(PowerManager.USER_ACTIVITY_FLAG_INDIRECT));
+ verify(mHostEmulationManager).onHostEmulationActivated();
+ verify(mPreferredServices).onHostEmulationActivated();
+ Assert.assertFalse(mCardEmulationManager.mNotSkipAid);
+ verifyZeroInteractions(mHostNfcFEmulationManager);
+ verifyZeroInteractions(mEnabledNfcFServices);
+ }
+
+ @Test
+ public void testOnHostCardEmulationActivated_technologyNfcf() {
+ mCardEmulationManager.onHostCardEmulationActivated(CardEmulationManager.NFC_HCE_NFCF);
+
+ assertConstructorMethodCalls();
+ verify(mPowerManager).userActivity(anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ eq(PowerManager.USER_ACTIVITY_FLAG_INDIRECT));
+ verify(mHostNfcFEmulationManager).onHostEmulationActivated();
+ verify(mRegisteredNfcFServicesCache).onHostEmulationActivated();
+ verify(mEnabledNfcFServices).onHostEmulationActivated();
+ verifyZeroInteractions(mHostEmulationManager);
+ verifyZeroInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testSkipAid_nullData_isFalse() {
+ mCardEmulationManager.mNotSkipAid = false;
+ Assert.assertFalse(mCardEmulationManager.isSkipAid(null));
+ }
+
+ @Test
+ public void testSkipAid_notSkipTrue_isFalse() {
+ mCardEmulationManager.mNotSkipAid = true;
+ Assert.assertFalse(mCardEmulationManager.isSkipAid(TEST_DATA_1));
+ }
+
+ @Test
+ public void testSkipAid_wrongData_isFalse() {
+ mCardEmulationManager.mNotSkipAid = false;
+ Assert.assertFalse(mCardEmulationManager.isSkipAid(TEST_DATA_1));
+ }
+
+ @Test
+ public void testSkipAid_ndf1_isTrue() {
+ mCardEmulationManager.mNotSkipAid = false;
+ Assert.assertTrue(mCardEmulationManager.isSkipAid(PROPER_SKIP_DATA_NDF1_HEADER));
+ }
+
+ @Test
+ public void testSkipAid_ndf2_isTrue() {
+ mCardEmulationManager.mNotSkipAid = false;
+ Assert.assertTrue(mCardEmulationManager.isSkipAid(PROPER_SKIP_DATA_NDF2_HEADER));
+ }
+
+ @Test
+ public void testOnHostCardEmulationData_technologyApdu_skipData() {
+ mCardEmulationManager.onHostCardEmulationData(CardEmulationManager.NFC_HCE_APDU,
+ PROPER_SKIP_DATA_NDF1_HEADER);
+
+ verify(mHostEmulationManager).onHostEmulationData(mDataCaptor.capture());
+ Assert.assertEquals(PROPER_SKIP_DATA_NDF1_HEADER, mDataCaptor.getValue());
+ verifyZeroInteractions(mHostNfcFEmulationManager);
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
+ public void testOnHostCardEmulationData_technologyNfcf_DontSkipData() {
+ mCardEmulationManager.onHostCardEmulationData(CardEmulationManager.NFC_HCE_NFCF,
+ PROPER_SKIP_DATA_NDF1_HEADER);
+
+ verify(mHostNfcFEmulationManager).onHostEmulationData(mDataCaptor.capture());
+ Assert.assertEquals(PROPER_SKIP_DATA_NDF1_HEADER, mDataCaptor.getValue());
+ verifyZeroInteractions(mHostEmulationManager);
+ verify(mPowerManager).userActivity(anyLong(), eq(PowerManager.USER_ACTIVITY_EVENT_TOUCH),
+ eq(0));
+ }
+
+ @Test
+ public void testOnHostCardEmulationDeactivated_technologyApdu() {
+ mCardEmulationManager.onHostCardEmulationDeactivated(CardEmulationManager.NFC_HCE_APDU);
+
+ assertConstructorMethodCalls();
+ verify(mHostEmulationManager).onHostEmulationDeactivated();
+ verify(mPreferredServices).onHostEmulationDeactivated();
+ verifyZeroInteractions(mHostNfcFEmulationManager);
+ verifyZeroInteractions(mRegisteredNfcFServicesCache);
+ verifyZeroInteractions(mEnabledNfcFServices);
+ }
+
+ @Test
+ public void testOnHostCardEmulationDeactivated_technologyNfcf() {
+ mCardEmulationManager.onHostCardEmulationDeactivated(CardEmulationManager.NFC_HCE_NFCF);
+
+ assertConstructorMethodCalls();
+ verify(mHostNfcFEmulationManager).onHostEmulationDeactivated();
+ verify(mRegisteredNfcFServicesCache).onHostEmulationDeactivated();
+ verify(mEnabledNfcFServices).onHostEmulationDeactivated();
+ verifyZeroInteractions(mHostEmulationManager);
+ verifyZeroInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testOnOffHostAidSelected() {
+ mCardEmulationManager.onOffHostAidSelected();
+
+ assertConstructorMethodCalls();
+ verify(mHostEmulationManager).onOffHostAidSelected();
+ }
+
+ @Test
+ public void testOnUserSwitched() {
+ mCardEmulationManager.onUserSwitched(USER_ID);
+
+ assertConstructorMethodCalls();
+ verify(mWalletRoleObserver).onUserSwitched(eq(USER_ID));
+ verify(mRegisteredServicesCache).onUserSwitched();
+ verify(mPreferredServices).onUserSwitched(eq(USER_ID));
+ verify(mHostNfcFEmulationManager).onUserSwitched();
+ verify(mRegisteredT3tIdentifiersCache).onUserSwitched();
+ verify(mEnabledNfcFServices).onUserSwitched(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache).onUserSwitched();
+ }
+
+ @Test
+ public void testOnManagedProfileChanged() {
+ mCardEmulationManager.onManagedProfileChanged();
+
+ assertConstructorMethodCalls();
+ verify(mRegisteredServicesCache).onManagedProfileChanged();
+ verify(mRegisteredNfcFServicesCache).onManagedProfileChanged();
+ }
+
+ @Test
+ public void testOnNfcEnabled() {
+ mCardEmulationManager.onNfcEnabled();
+
+ assertConstructorMethodCalls();
+ verify(mRegisteredAidCache).onNfcEnabled();
+ verify(mRegisteredT3tIdentifiersCache).onNfcEnabled();
+ }
+
+ @Test
+ public void testOnNfcDisabled() {
+ mCardEmulationManager.onNfcDisabled();
+
+ assertConstructorMethodCalls();
+ verify(mRegisteredAidCache).onNfcDisabled();
+ verify(mHostNfcFEmulationManager).onNfcDisabled();
+ verify(mRegisteredNfcFServicesCache).onNfcDisabled();
+ verify(mEnabledNfcFServices).onNfcDisabled();
+ verify(mRegisteredT3tIdentifiersCache).onNfcDisabled();
+ }
+
+ @Test
+ public void testOnSecureNfcToggled() {
+ mCardEmulationManager.onSecureNfcToggled();
+
+ verify(mRegisteredAidCache).onSecureNfcToggled();
+ verify(mRegisteredT3tIdentifiersCache).onSecureNfcToggled();
+ }
+
+ @Test
+ public void testOnServicesUpdated_walletEnabledPollingLoopDisabled() {
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(android.nfc.Flags.nfcReadPollingLoop()).thenReturn(false);
+
+ mCardEmulationManager.onServicesUpdated(USER_ID, UPDATED_SERVICES, false);
+
+ verify(mWalletRoleObserver, times(2)).isWalletRoleFeatureEnabled();
+ verify(mRegisteredAidCache).onServicesUpdated(eq(USER_ID), mServiceListCaptor.capture());
+ verify(mPreferredServices).onServicesUpdated();
+ Assert.assertEquals(UPDATED_SERVICES, mServiceListCaptor.getValue());
+ verifyZeroInteractions(mHostEmulationManager);
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_UPDATED));
+ }
+
+ @Test
+ public void testOnServicesUpdated_walletEnabledPollingLoopEnabled() {
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(android.nfc.Flags.nfcReadPollingLoop()).thenReturn(true);
+
+ mCardEmulationManager.onServicesUpdated(USER_ID, UPDATED_SERVICES, false);
+
+ verify(mWalletRoleObserver, times(2)).isWalletRoleFeatureEnabled();
+ verify(mRegisteredAidCache).onServicesUpdated(eq(USER_ID), mServiceListCaptor.capture());
+ verify(mPreferredServices).onServicesUpdated();
+ verify(mHostEmulationManager).updatePollingLoopFilters(eq(USER_ID),
+ mServiceListCaptor.capture());
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_UPDATED));
+ Assert.assertEquals(UPDATED_SERVICES, mServiceListCaptor.getAllValues().getFirst());
+ Assert.assertEquals(UPDATED_SERVICES, mServiceListCaptor.getAllValues().getLast());
+ }
+
+ @Test
+ public void testOnNfcFServicesUpdated() {
+ mCardEmulationManager.onNfcFServicesUpdated(USER_ID, UPDATED_NFC_SERVICES);
+
+ verify(mRegisteredT3tIdentifiersCache).onServicesUpdated(eq(USER_ID),
+ mNfcServiceListCaptor.capture());
+ Assert.assertEquals(UPDATED_NFC_SERVICES, mNfcServiceListCaptor.getValue());
+ }
+
+ @Test
+ public void testIsServiceRegistered_serviceExists() {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager
+ .isServiceRegistered(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testIsServiceRegistered_serviceDoesNotExists() {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager
+ .isServiceRegistered(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testIsNfcServiceInstalled_serviceExists() {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager
+ .isNfcFServiceInstalled(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testIsNfcServiceInstalled_serviceDoesNotExists() {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager
+ .isNfcFServiceInstalled(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testPackageHasPreferredService() {
+ when(mPreferredServices.packageHasPreferredService(eq(WALLET_HOLDER_PACKAGE_NAME)))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager
+ .packageHasPreferredService(WALLET_HOLDER_PACKAGE_NAME));
+
+ verify(mPreferredServices).packageHasPreferredService(eq(WALLET_HOLDER_PACKAGE_NAME));
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultServiceForCategory_serviceExistsWalletEnabled()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(mWalletRoleObserver.getDefaultWalletRoleHolder(eq(USER_ID)))
+ .thenReturn(WALLET_HOLDER_PACKAGE_NAME);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultServiceForCategory(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultServiceForCategory_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ assertConstructorMethodCalls();
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultServiceForCategory(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verifyZeroInteractions(mWalletRoleObserver);
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultServiceForAid_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredAidCache.isDefaultServiceForAid(eq(USER_ID), any(), eq(PAYMENT_AID_1)))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultServiceForAid(USER_ID, WALLET_PAYMENT_SERVICE,
+ PAYMENT_AID_1));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredAidCache).isDefaultServiceForAid(eq(USER_ID), eq(WALLET_PAYMENT_SERVICE),
+ eq(PAYMENT_AID_1));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultServiceForAid_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultServiceForAid(USER_ID, WALLET_PAYMENT_SERVICE,
+ PAYMENT_AID_1));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyZeroInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationSetDefaultForNextTap_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mPreferredServices.setDefaultForNextTap(anyInt(), any())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setDefaultForNextTap(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateProfileId(mContext, USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ });
+ verify(mPreferredServices).setDefaultForNextTap(eq(USER_ID), eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ }
+
+ @Test
+ public void testCardEmulationSetDefaultForNextTap_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setDefaultForNextTap(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateProfileId(mContext, USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mPreferredServices).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verifyZeroInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testCardEmulationSetShouldDefaultToObserveModeForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.setShouldDefaultToObserveModeForService(anyInt(), anyInt(),
+ any(), anyBoolean())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setShouldDefaultToObserveModeForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ true));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).setShouldDefaultToObserveModeForService(eq(USER_ID),
+ anyInt(), eq(WALLET_PAYMENT_SERVICE), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetShouldDefaultToObserveModeForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setShouldDefaultToObserveModeForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ false));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterAidGroupForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.registerAidGroupForService(eq(USER_ID), anyInt(), any(),
+ any())).thenReturn(true);
+ AidGroup aidGroup = Mockito.mock(AidGroup.class);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE, aidGroup));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).registerAidGroupForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE), eq(aidGroup));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterAidGroupForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.registerAidGroupForService(eq(USER_ID), anyInt(), any(),
+ any())).thenReturn(true);
+ AidGroup aidGroup = Mockito.mock(AidGroup.class);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE, aidGroup));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterPollingLoopFilterForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.registerPollingLoopFilterForService(eq(USER_ID), anyInt(),
+ any(), any(),anyBoolean())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerPollingLoopFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter, true));
+
+ verify(mRegisteredServicesCache).initialize();
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).registerPollingLoopFilterForService(eq(USER_ID),
+ anyInt(), eq(WALLET_PAYMENT_SERVICE), eq(pollingLoopFilter), eq(true));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterPollingLoopFilterForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.registerPollingLoopFilterForService(eq(USER_ID), anyInt(),
+ any(), any(),anyBoolean())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerPollingLoopFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter, true));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRemovePollingLoopFilterForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.removePollingLoopFilterForService(eq(USER_ID), anyInt(),
+ any(), any())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removePollingLoopFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).removePollingLoopFilterForService(eq(USER_ID),
+ anyInt(), eq(WALLET_PAYMENT_SERVICE), eq(pollingLoopFilter));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRemovePollingLoopFilterForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.removePollingLoopFilterForService(eq(USER_ID), anyInt(),
+ any(), any())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removePollingLoopFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterPollingLoopPatternFilterForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.registerPollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), any(), any(), anyBoolean())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerPollingLoopPatternFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter, true));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).registerPollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), eq(WALLET_PAYMENT_SERVICE), eq(pollingLoopFilter), eq(true));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRegisterPollingLoopPatternFilterForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.registerPollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), any(), any(), anyBoolean())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .registerPollingLoopPatternFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter, true));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRemovePollingLoopPatternFilterForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.removePollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), any(), any())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removePollingLoopPatternFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).removePollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), eq(WALLET_PAYMENT_SERVICE), eq(pollingLoopFilter));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRemovePollingLoopPatternFilterForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.removePollingLoopPatternFilterForService(eq(USER_ID),
+ anyInt(), any(), any())).thenReturn(true);
+ String pollingLoopFilter = "filter";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removePollingLoopPatternFilterForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ pollingLoopFilter));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetOffHostForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.setOffHostSecureElement(eq(USER_ID),
+ anyInt(), any(), any())).thenReturn(true);
+ String offhostse = "offhostse";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setOffHostForService(USER_ID, WALLET_PAYMENT_SERVICE, offhostse));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).setOffHostSecureElement(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE) , eq(offhostse));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetOffHostForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.setOffHostSecureElement(eq(USER_ID),
+ anyInt(), any(), any())).thenReturn(true);
+ String offhostse = "offhostse";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setOffHostForService(USER_ID, WALLET_PAYMENT_SERVICE, offhostse));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationUnsetOffHostForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.resetOffHostSecureElement(eq(USER_ID),
+ anyInt(), any())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .unsetOffHostForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).resetOffHostSecureElement(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_UPDATED));
+ }
+
+ @Test
+ public void testCardEmulationUnsetOffHostForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.resetOffHostSecureElement(eq(USER_ID),
+ anyInt(), any())).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .unsetOffHostForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationGetAidGroupForService_serviceExists()
+ throws RemoteException {
+ AidGroup aidGroup = Mockito.mock(AidGroup.class);
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.getAidGroupForService(eq(USER_ID),
+ anyInt(), any(), eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(aidGroup);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcCardEmulationInterface()
+ .getAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT), aidGroup);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).getAidGroupForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE), eq(CardEmulation.CATEGORY_PAYMENT));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationGetAidGroupForService_serviceDoesNotExists()
+ throws RemoteException {
+ AidGroup aidGroup = Mockito.mock(AidGroup.class);
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.getAidGroupForService(eq(USER_ID),
+ anyInt(), any(), eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(aidGroup);
+
+ Assert.assertNull(mCardEmulationManager.getNfcCardEmulationInterface()
+ .getAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationRemoveAidGroupForService_serviceExists()
+ throws RemoteException {
+ AidGroup aidGroup = Mockito.mock(AidGroup.class);
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mRegisteredServicesCache.removeAidGroupForService(eq(USER_ID),
+ anyInt(), any(), eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removeAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).removeAidGroupForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE), eq(CardEmulation.CATEGORY_PAYMENT));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_UPDATED));
+ }
+
+ @Test
+ public void testCardEmulationRemoveAidGroupForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.removeAidGroupForService(eq(USER_ID),
+ anyInt(), any(), eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .removeAidGroupForService(USER_ID, WALLET_PAYMENT_SERVICE,
+ CardEmulation.CATEGORY_PAYMENT));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationGetServices()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mRegisteredServicesCache.getServicesForCategory(eq(USER_ID),
+ eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(UPDATED_SERVICES);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcCardEmulationInterface()
+ .getServices(USER_ID, CardEmulation.CATEGORY_PAYMENT), UPDATED_SERVICES);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateProfileId(mContext, USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceAdminPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).getServicesForCategory(eq(USER_ID),
+ eq(CardEmulation.CATEGORY_PAYMENT));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetPreferredService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mPreferredServices.registerPreferredForegroundService(eq(WALLET_PAYMENT_SERVICE),
+ anyInt())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setPreferredService(WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ verify(mPreferredServices).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mPreferredServices).registerPreferredForegroundService(eq(WALLET_PAYMENT_SERVICE),
+ anyInt());
+ verifyNoMoreInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testCardEmulationSetPreferredService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(false);
+ when(mPreferredServices.registerPreferredForegroundService(eq(WALLET_PAYMENT_SERVICE),
+ anyInt())).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setPreferredService(WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).invalidateCache(eq(USER_ID), eq(true));
+ verify(mRegisteredServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ verify(mPreferredServices).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verifyNoMoreInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testCardEmulationUnsetPreferredService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredServicesCache.hasService(eq(USER_ID), any())).thenReturn(true);
+ when(mPreferredServices.unregisteredPreferredForegroundService(anyInt()))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .unsetPreferredService());
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mPreferredServices).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mPreferredServices).unregisteredPreferredForegroundService(anyInt());
+ verifyNoMoreInteractions(mPreferredServices);
+ }
+
+ @Test
+ public void testCardEmulationUnsetPreferredService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mPreferredServices.unregisteredPreferredForegroundService(anyInt()))
+ .thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .unsetPreferredService());
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mPreferredServices).unregisteredPreferredForegroundService(anyInt());
+ }
+
+ @Test
+ public void testCardEmulationSupportsAidPrefixRegistration_doesSupport()
+ throws RemoteException {
+ when(mRegisteredAidCache.supportsAidPrefixRegistration()).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .supportsAidPrefixRegistration());
+
+ verify(mRegisteredAidCache).supportsAidPrefixRegistration();
+ }
+
+ @Test
+ public void testCardEmulationSupportsAidPrefixRegistration_doesNotSupport()
+ throws RemoteException {
+ when(mRegisteredAidCache.supportsAidPrefixRegistration()).thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .supportsAidPrefixRegistration());
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRegisteredAidCache).supportsAidPrefixRegistration();
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationGetPreferredPaymentService()
+ throws RemoteException {
+ ApduServiceInfo apduServiceInfo = Mockito.mock(ApduServiceInfo.class);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(mRegisteredServicesCache.getService(eq(USER_ID), eq(WALLET_PAYMENT_SERVICE)))
+ .thenReturn(apduServiceInfo);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcCardEmulationInterface()
+ .getPreferredPaymentService(USER_ID), apduServiceInfo);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforcePreferredPaymentInfoPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRegisteredAidCache).getPreferredService();
+ verify(mRegisteredServicesCache).getService(eq(USER_ID), eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetServiceEnabledForCategoryOther_resourceTrue()
+ throws RemoteException {
+ when(mResources.getBoolean(R.bool.enable_service_for_category_other)).thenReturn(true);
+ when(mRegisteredServicesCache.registerOtherForService(anyInt(), any(), anyBoolean()))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setServiceEnabledForCategoryOther(USER_ID, WALLET_PAYMENT_SERVICE, true));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredServicesCache).initialize();
+ verify(mRegisteredServicesCache).registerOtherForService(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE), eq(true));
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationSetServiceEnabledForCategoryOther_resourceFalse()
+ throws RemoteException {
+ when(mResources.getBoolean(R.bool.enable_service_for_category_other)).thenReturn(false);
+ when(mRegisteredServicesCache.registerOtherForService(anyInt(), any(), anyBoolean()))
+ .thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .setServiceEnabledForCategoryOther(USER_ID, WALLET_PAYMENT_SERVICE, true));
+
+ verify(mRegisteredServicesCache).initialize();
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultPaymentRegistered_walletRoleEnabledWalletSet()
+ throws RemoteException {
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(mWalletRoleObserver.getDefaultWalletRoleHolder(anyInt()))
+ .thenReturn(WALLET_HOLDER_PACKAGE_NAME);
+ when(Binder.getCallingUserHandle()).thenReturn(USER_HANDLE);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultPaymentRegistered());
+
+ verify(mWalletRoleObserver, times(2)).isWalletRoleFeatureEnabled();
+ verify(mWalletRoleObserver, times(2))
+ .getDefaultWalletRoleHolder(eq(USER_ID));
+ }
+
+ @Test
+ public void testCardEmulationIsDefaultPaymentRegistered_walletRoleEnabledWalletNone()
+ throws RemoteException {
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(mWalletRoleObserver.getDefaultWalletRoleHolder(anyInt()))
+ .thenReturn(null);
+ when(Binder.getCallingUserHandle()).thenReturn(USER_HANDLE);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .isDefaultPaymentRegistered());
+
+ verify(mWalletRoleObserver, times(2)).isWalletRoleFeatureEnabled();
+ verify(mWalletRoleObserver, times(2))
+ .getDefaultWalletRoleHolder(eq(USER_ID));
+ }
+
+ @Test
+ public void testCardEmulationOverrideRoutingTable_callerNotForeground()
+ throws RemoteException {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt()))
+ .thenReturn(false);
+ String protocol = "DH";
+ String technology = "DH";
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .overrideRoutingTable(USER_ID, protocol, technology));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationOverrideRoutingTable_callerForegroundRouteNull()
+ throws RemoteException {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt()))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .overrideRoutingTable(USER_ID, null, null));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).overrideDefaultIsoDepRoute(eq(-1));
+ verify(mRoutingOptionManager).overrideDefaultOffHostRoute(eq(-1));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredAidCache).onRoutingOverridedOrRecovered();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationOverrideRoutingTable_callerForegroundRouteDH()
+ throws RemoteException {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt()))
+ .thenReturn(true);
+ String protocol = "DH";
+ String technology = "DH";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .overrideRoutingTable(USER_ID, protocol, technology));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).overrideDefaultIsoDepRoute(eq(0));
+ verify(mRoutingOptionManager).overrideDefaultOffHostRoute(eq(0));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredAidCache).onRoutingOverridedOrRecovered();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationOverrideRoutingTable_callerForegroundRouteeSE()
+ throws RemoteException {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt()))
+ .thenReturn(true);
+ String protocol = "eSE1";
+ String technology = "eSE1";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .overrideRoutingTable(USER_ID, protocol, technology));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).overrideDefaultIsoDepRoute(eq(TEST_DATA_1[0] & 0xFF));
+ verify(mRoutingOptionManager).overrideDefaultOffHostRoute(eq(TEST_DATA_1[0] & 0xFF));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredAidCache).onRoutingOverridedOrRecovered();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationOverrideRoutingTable_callerForegroundRouteSIM()
+ throws RemoteException {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt()))
+ .thenReturn(true);
+ String protocol = "SIM1";
+ String technology = "SIM1";
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .overrideRoutingTable(USER_ID, protocol, technology));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).overrideDefaultIsoDepRoute(eq(TEST_DATA_2[0] & 0xFF));
+ verify(mRoutingOptionManager).overrideDefaultOffHostRoute(eq(TEST_DATA_2[0] & 0xFF));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredAidCache).onRoutingOverridedOrRecovered();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationRecoverRoutingTable_callerForeground()
+ throws RemoteException {
+ when(mForegroundUtils.isInForeground(anyInt()))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcCardEmulationInterface()
+ .recoverRoutingTable(USER_ID));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).recoverOverridedRoutingTable();
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verify(mRegisteredAidCache).onRoutingOverridedOrRecovered();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testCardEmulationRecoverRoutingTable_callerNotForeground()
+ throws RemoteException {
+ when(mForegroundUtils.isInForeground(anyInt()))
+ .thenReturn(false);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcCardEmulationInterface()
+ .recoverRoutingTable(USER_ID));
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME),
+ eq(USER_ID));
+ verify(mRoutingOptionManager).getOffHostRouteEse();
+ verify(mRoutingOptionManager).getOffHostRouteUicc();
+ verifyNoMoreInteractions(mRoutingOptionManager);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetSystemCodeForService_serviceExists()
+ throws RemoteException {
+ String systemCode = "systemCode";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mRegisteredNfcFServicesCache.getSystemCodeForService(anyInt(),
+ anyInt(), any())).thenReturn(systemCode);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE), systemCode);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredNfcFServicesCache).getSystemCodeForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetSystemCodeForService_serviceDoesNotExists()
+ throws RemoteException {
+ String systemCode = "systemCode";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mRegisteredNfcFServicesCache.getSystemCodeForService(anyInt(),
+ anyInt(), any())).thenReturn(systemCode);
+
+ Assert.assertNull(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationRegisterSystemCodeForService_serviceExists()
+ throws RemoteException {
+ String systemCode = "systemCode";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mRegisteredNfcFServicesCache.registerSystemCodeForService(anyInt(),
+ anyInt(), any(), anyString())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .registerSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE, systemCode));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredNfcFServicesCache).registerSystemCodeForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE), eq(systemCode));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationRegisterSystemCodeForService_serviceDoesNotExists()
+ throws RemoteException {
+ String systemCode = "systemCode";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mRegisteredNfcFServicesCache.registerSystemCodeForService(anyInt(),
+ anyInt(), any(), anyString())).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .registerSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE, systemCode));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationRemoveSystemCodeForService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mRegisteredNfcFServicesCache.removeSystemCodeForService(anyInt(),
+ anyInt(), any())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .removeSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredNfcFServicesCache).removeSystemCodeForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationRemoveSystemCodeForService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mRegisteredNfcFServicesCache.removeSystemCodeForService(anyInt(),
+ anyInt(), any())).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .removeSystemCodeForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetNfcid2ForService_serviceExists()
+ throws RemoteException {
+ String nfcid2 = "nfcid2";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mRegisteredNfcFServicesCache.getNfcid2ForService(anyInt(),
+ anyInt(), any())).thenReturn(nfcid2);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getNfcid2ForService(USER_ID, WALLET_PAYMENT_SERVICE), nfcid2);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredNfcFServicesCache).getNfcid2ForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetNfcid2ForService_serviceDoesNotExists()
+ throws RemoteException {
+ String nfcid2 = "nfcid2";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mRegisteredNfcFServicesCache.getNfcid2ForService(anyInt(),
+ anyInt(), any())).thenReturn(nfcid2);
+
+ Assert.assertNull(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getNfcid2ForService(USER_ID, WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationSetNfcid2ForService_serviceExists()
+ throws RemoteException {
+ String nfcid2 = "nfcid2";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mRegisteredNfcFServicesCache.setNfcid2ForService(anyInt(),
+ anyInt(), any(), anyString())).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .setNfcid2ForService(USER_ID, WALLET_PAYMENT_SERVICE, nfcid2));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredNfcFServicesCache).setNfcid2ForService(eq(USER_ID), anyInt(),
+ eq(WALLET_PAYMENT_SERVICE), eq(nfcid2));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationSetNfcid2ForService_serviceDoesNotExists()
+ throws RemoteException {
+ String nfcid2 = "nfcid2";
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mRegisteredNfcFServicesCache.setNfcid2ForService(anyInt(),
+ anyInt(), any(), anyString())).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .setNfcid2ForService(USER_ID, WALLET_PAYMENT_SERVICE, nfcid2));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateUserId(USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationEnableNfcFForegroundService_serviceExists()
+ throws RemoteException {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(true);
+ when(mEnabledNfcFServices.registerEnabledForegroundService(any(),
+ anyInt())).thenReturn(true);
+ when(Binder.getCallingUserHandle()).thenReturn(USER_HANDLE);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .enableNfcFForegroundService(WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verify(mEnabledNfcFServices).registerEnabledForegroundService(eq(WALLET_PAYMENT_SERVICE),
+ anyInt());
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationEnableNfcFForegroundService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mRegisteredNfcFServicesCache.hasService(eq(USER_ID), any()))
+ .thenReturn(false);
+ when(mEnabledNfcFServices.registerEnabledForegroundService(any(),
+ anyInt())).thenReturn(true);
+
+ Assert.assertFalse(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .enableNfcFForegroundService(WALLET_PAYMENT_SERVICE));
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).invalidateCache(eq(USER_ID));
+ verify(mRegisteredNfcFServicesCache, times(2))
+ .hasService(eq(USER_ID),eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ verifyNoMoreInteractions(mEnabledNfcFServices);
+ }
+
+ @Test
+ public void testNfcFCardEmulationDisableNfcFForegroundService_serviceDoesNotExists()
+ throws RemoteException {
+ when(mEnabledNfcFServices.unregisteredEnabledForegroundService(anyInt()))
+ .thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .disableNfcFForegroundService());
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mEnabledNfcFServices).unregisteredEnabledForegroundService(anyInt());
+ verifyNoMoreInteractions(mEnabledNfcFServices);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetServices()
+ throws RemoteException {
+ when(mRegisteredNfcFServicesCache.getServices(anyInt()))
+ .thenReturn(UPDATED_NFC_SERVICES);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getNfcFServices(USER_ID), UPDATED_NFC_SERVICES);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.validateProfileId(mContext, USER_ID);
+ });
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mRegisteredNfcFServicesCache).initialize();
+ verify(mRegisteredNfcFServicesCache).getServices(eq(USER_ID));
+ verifyNoMoreInteractions(mRegisteredNfcFServicesCache);
+ }
+
+ @Test
+ public void testNfcFCardEmulationGetMaxNumOfRegisterableSystemCodes()
+ throws RemoteException {
+ when(mNfcService.getLfT3tMax()).thenReturn(3);
+
+ Assert.assertEquals(mCardEmulationManager.getNfcFCardEmulationInterface()
+ .getMaxNumOfRegisterableSystemCodes(), 3);
+
+ ExtendedMockito.verify(() -> {
+ NfcPermissions.enforceUserPermissions(mContext);
+ });
+ verify(mNfcService).getLfT3tMax();
+ verifyNoMoreInteractions(mNfcService);
+ }
+
+ @Test
+ public void testOnPreferredPaymentServiceChanged_observeModeEnabled() {
+ when(mRegisteredServicesCache.doesServiceShouldDefaultToObserveMode(anyInt(), any()))
+ .thenReturn(true);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(android.nfc.Flags.nfcObserveMode()).thenReturn(true);
+
+ mCardEmulationManager.onPreferredPaymentServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mHostEmulationManager).onPreferredPaymentServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(
+ eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verify(mRegisteredAidCache).onPreferredPaymentServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).initialize();
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_CHANGED));
+ assertUpdateForShouldDefaultToObserveMode(true);
+ }
+
+ @Test
+ public void testOnPreferredPaymentServiceChanged_observeModeDisabled() {
+ when(mRegisteredServicesCache.doesServiceShouldDefaultToObserveMode(anyInt(), any()))
+ .thenReturn(true);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(android.nfc.Flags.nfcObserveMode()).thenReturn(false);
+
+ mCardEmulationManager.onPreferredPaymentServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mHostEmulationManager).onPreferredPaymentServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(
+ eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verify(mRegisteredAidCache).onPreferredPaymentServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).initialize();
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_CHANGED));
+ assertUpdateForShouldDefaultToObserveMode(false);
+ }
+
+ @Test
+ public void testOnPreferredForegroundServiceChanged_observeModeEnabled() {
+ when(mRegisteredServicesCache.doesServiceShouldDefaultToObserveMode(anyInt(), any()))
+ .thenReturn(true);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(android.nfc.Flags.nfcObserveMode()).thenReturn(true);
+
+ mCardEmulationManager.onPreferredForegroundServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(
+ eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verify(mHostEmulationManager).onPreferredForegroundServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredAidCache).onPreferredForegroundServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).initialize();
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_CHANGED));
+ assertUpdateForShouldDefaultToObserveMode(true);
+ }
+
+ @Test
+ public void testOnPreferredForegroundServiceChanged_observeModeDisabled() {
+ when(mRegisteredServicesCache.doesServiceShouldDefaultToObserveMode(anyInt(), any()))
+ .thenReturn(true);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(android.nfc.Flags.nfcObserveMode()).thenReturn(false);
+
+ mCardEmulationManager.onPreferredForegroundServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mHostEmulationManager).onPreferredForegroundServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredAidCache).onWalletRoleHolderChanged(
+ eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verify(mRegisteredAidCache).onPreferredForegroundServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mRegisteredServicesCache).initialize();
+ verify(mNfcService).onPreferredPaymentChanged(eq(NfcAdapter.PREFERRED_PAYMENT_CHANGED));
+ assertUpdateForShouldDefaultToObserveMode(false);
+ }
+
+ @Test
+ public void testOnWalletRoleHolderChanged() {
+ mCardEmulationManager.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+
+ verify(mPreferredServices, times(2))
+ .onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verify(mRegisteredAidCache, times(2))
+ .onWalletRoleHolderChanged(eq(WALLET_HOLDER_PACKAGE_NAME), eq(USER_ID));
+ verifyNoMoreInteractions(mPreferredServices);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ @Test
+ public void testOnEnabledForegroundNfcFServiceChanged() {
+ mCardEmulationManager.onEnabledForegroundNfcFServiceChanged(USER_ID,
+ WALLET_PAYMENT_SERVICE);
+
+ verify(mRegisteredT3tIdentifiersCache).onEnabledForegroundNfcFServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mHostNfcFEmulationManager)
+ .onEnabledForegroundNfcFServiceChanged(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verifyNoMoreInteractions(mRegisteredT3tIdentifiersCache);
+ verifyNoMoreInteractions(mHostNfcFEmulationManager);
+ }
+
+ @Test
+ public void testGetRegisteredAidCategory() {
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = Mockito.mock(
+ RegisteredAidCache.AidResolveInfo.class);
+ when(aidResolveInfo.getCategory()).thenReturn(CardEmulation.CATEGORY_PAYMENT);
+
+ when(mRegisteredAidCache.resolveAid(anyString())).thenReturn(aidResolveInfo);
+
+ Assert.assertEquals(mCardEmulationManager.getRegisteredAidCategory(PAYMENT_AID_1),
+ CardEmulation.CATEGORY_PAYMENT);
+
+ verify(mRegisteredAidCache).resolveAid(eq(PAYMENT_AID_1));
+ verify(aidResolveInfo).getCategory();
+ }
+
+ @Test
+ public void testIsRequiresScreenOnServiceExist() {
+ when(mRegisteredAidCache.isRequiresScreenOnServiceExist()).thenReturn(true);
+
+ Assert.assertTrue(mCardEmulationManager.isRequiresScreenOnServiceExist());
+ }
+
+
+ private void assertUpdateForShouldDefaultToObserveMode(boolean flagEnabled) {
+ if (flagEnabled) {
+ ExtendedMockito.verify(() -> {
+ NfcAdapter.getDefaultAdapter(mContext);
+ });
+ verify(mRegisteredAidCache).getPreferredService();
+ verify(mRegisteredServicesCache).doesServiceShouldDefaultToObserveMode(eq(USER_ID),
+ eq(WALLET_PAYMENT_SERVICE));
+ verify(mNfcAdapter).setObserveModeEnabled(eq(true));
+ }
+ verifyNoMoreInteractions(mNfcAdapter);
+ verifyNoMoreInteractions(mRegisteredServicesCache);
+ verifyNoMoreInteractions(mRegisteredAidCache);
+ }
+
+ private CardEmulationManager createInstanceWithMockParams() {
+ when(mRoutingOptionManager.getOffHostRouteEse()).thenReturn(TEST_DATA_1);
+ when(mRoutingOptionManager.getOffHostRouteUicc()).thenReturn(TEST_DATA_2);
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ when(mWalletRoleObserver.getDefaultWalletRoleHolder(eq(USER_ID)))
+ .thenReturn(WALLET_HOLDER_PACKAGE_NAME);
+
+ return new CardEmulationManager(mContext, mForegroundUtils, mWalletRoleObserver,
+ mRegisteredAidCache, mRegisteredT3tIdentifiersCache, mHostEmulationManager,
+ mHostNfcFEmulationManager, mRegisteredServicesCache, mRegisteredNfcFServicesCache,
+ mPreferredServices, mEnabledNfcFServices, mRoutingOptionManager, mPowerManager);
+ }
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/HostEmulationManagerTest.java b/tests/unit/src/com/android/nfc/cardemulation/HostEmulationManagerTest.java
new file mode 100644
index 0000000..efda7e5
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/HostEmulationManagerTest.java
@@ -0,0 +1,1196 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.app.KeyguardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.PollingFrame;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.NfcEventLog;
+import com.android.nfc.NfcInjector;
+import com.android.nfc.NfcService;
+import com.android.nfc.NfcStatsLog;
+import com.android.nfc.cardemulation.util.StatsdUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class HostEmulationManagerTest {
+
+ private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
+ private static final ComponentName WALLET_PAYMENT_SERVICE
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+ private static final ComponentName ANOTHER_WALLET_SERVICE
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.XRoleHolderApduService");
+ private static final int USER_ID = 0;
+ private static final UserHandle USER_HANDLE = UserHandle.of(USER_ID);
+ private static final String PL_FILTER = "66696C746572";
+ private static final Pattern PL_PATTERN = Pattern.compile("66696C*46572");
+ private static final List<String> POLLING_LOOP_FILTER = List.of(PL_FILTER);
+ private static final List<Pattern> POLLING_LOOP_PATTEN_FILTER
+ = List.of(PL_PATTERN);
+ private static final String MOCK_AID = "A000000476416E64726F6964484340";
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private RegisteredAidCache mRegisteredAidCache;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private KeyguardManager mKeyguardManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private NfcAdapter mNfcAdapter;
+ @Mock
+ private Messenger mMessanger;
+ @Mock
+ private NfcService mNfcService;
+ @Mock
+ private NfcInjector mNfcInjector;
+ @Mock
+ private NfcEventLog mNfcEventLog;
+ @Mock
+ private StatsdUtils mStatsUtils;
+ @Captor
+ private ArgumentCaptor<Intent> mIntentArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<ServiceConnection> mServiceConnectionArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<List<ApduServiceInfo>> mServiceListArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<Message> mMessageArgumentCaptor;
+
+ private MockitoSession mStaticMockSession;
+ private TestableLooper mTestableLooper;
+ private HostEmulationManager mHostEmulationManager;
+
+ @Before
+ public void setUp() {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(com.android.nfc.flags.Flags.class)
+ .mockStatic(NfcStatsLog.class)
+ .mockStatic(UserHandle.class)
+ .mockStatic(NfcAdapter.class)
+ .mockStatic(NfcService.class)
+ .mockStatic(NfcInjector.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+ when(NfcAdapter.getDefaultAdapter(mContext)).thenReturn(mNfcAdapter);
+ when(UserHandle.getUserHandleForUid(eq(USER_ID))).thenReturn(USER_HANDLE);
+ when(UserHandle.of(eq(USER_ID))).thenReturn(USER_HANDLE);
+ when(NfcService.getInstance()).thenReturn(mNfcService);
+ when(NfcInjector.getInstance()).thenReturn(mNfcInjector);
+ when(mNfcInjector.getNfcEventLog()).thenReturn(mNfcEventLog);
+ when(com.android.nfc.flags.Flags.statsdCeEventsFlag()).thenReturn(true);
+ when(mContext.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManager);
+ when(mContext.getSystemService(eq(KeyguardManager.class))).thenReturn(mKeyguardManager);
+ mHostEmulationManager = new HostEmulationManager(mContext, mTestableLooper.getLooper(),
+ mRegisteredAidCache, mStatsUtils);
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructor() {
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnPreferredPaymentServiceChanged_nullService() {
+ mHostEmulationManager.mPaymentServiceBound = true;
+
+ mHostEmulationManager.onPreferredPaymentServiceChanged(USER_ID, null);
+ mTestableLooper.processAllMessages();
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).unbindService(eq(mHostEmulationManager.getPaymentConnection()));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnPreferredPaymentServiceChanged_noPreviouslyBoundService() {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ UserHandle userHandle = UserHandle.of(USER_ID);
+
+ mHostEmulationManager.onPreferredPaymentServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+ mTestableLooper.processAllMessages();
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(userHandle));
+ verifyNoMoreInteractions(mContext);
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertTrue(mHostEmulationManager.mPaymentServiceBound);
+ Assert.assertEquals(mHostEmulationManager.mLastBoundPaymentServiceName,
+ WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(mHostEmulationManager.mPaymentServiceUserId,
+ USER_ID);
+ }
+
+ @Test
+ public void testOnPreferredPaymentServiceChanged_previouslyBoundService() {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ UserHandle userHandle = UserHandle.of(USER_ID);
+ mHostEmulationManager.mPaymentServiceBound = true;
+
+ mHostEmulationManager.onPreferredPaymentServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+ mTestableLooper.processAllMessages();
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).unbindService(eq(mHostEmulationManager.getPaymentConnection()));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(userHandle));
+ verifyNoMoreInteractions(mContext);
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertTrue(mHostEmulationManager.mPaymentServiceBound);
+ Assert.assertEquals(mHostEmulationManager.mLastBoundPaymentServiceName,
+ WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(mHostEmulationManager.mPaymentServiceUserId,
+ USER_ID);
+ Assert.assertNotNull(mServiceConnectionArgumentCaptor.getValue());
+ }
+
+ @Test
+ public void testUpdatePollingLoopFilters() {
+ ApduServiceInfo serviceWithFilter = mock(ApduServiceInfo.class);
+ when(serviceWithFilter.getPollingLoopFilters()).thenReturn(POLLING_LOOP_FILTER);
+ when(serviceWithFilter.getPollingLoopPatternFilters()).thenReturn(List.of());
+ ApduServiceInfo serviceWithPatternFilter = mock(ApduServiceInfo.class);
+ when(serviceWithPatternFilter.getPollingLoopFilters()).thenReturn(List.of());
+ when(serviceWithPatternFilter.getPollingLoopPatternFilters())
+ .thenReturn(POLLING_LOOP_PATTEN_FILTER);
+
+ mHostEmulationManager.updatePollingLoopFilters(USER_ID, List.of(serviceWithFilter,
+ serviceWithPatternFilter));
+
+ Map<Integer, Map<String, List<ApduServiceInfo>>> pollingLoopFilters
+ = mHostEmulationManager.getPollingLoopFilters();
+ Map<Integer, Map<Pattern, List<ApduServiceInfo>>> pollingLoopPatternFilters
+ = mHostEmulationManager.getPollingLoopPatternFilters();
+ Assert.assertTrue(pollingLoopFilters.containsKey(USER_ID));
+ Assert.assertTrue(pollingLoopPatternFilters.containsKey(USER_ID));
+ Map<String, List<ApduServiceInfo>> filtersForUser = pollingLoopFilters.get(USER_ID);
+ Map<Pattern, List<ApduServiceInfo>> patternFiltersForUser = pollingLoopPatternFilters
+ .get(USER_ID);
+ Assert.assertTrue(filtersForUser.containsKey(PL_FILTER));
+ Assert.assertTrue(patternFiltersForUser.containsKey(PL_PATTERN));
+ Assert.assertTrue(filtersForUser.get(PL_FILTER).contains(serviceWithFilter));
+ Assert.assertTrue(patternFiltersForUser.get(PL_PATTERN).contains(serviceWithPatternFilter));
+ }
+
+ @Test
+ public void testOnPollingLoopDetected_activeServiceAlreadyBound_overlappingServices()
+ throws PackageManager.NameNotFoundException, RemoteException {
+ ApduServiceInfo serviceWithFilter = mock(ApduServiceInfo.class);
+ when(serviceWithFilter.getPollingLoopFilters()).thenReturn(POLLING_LOOP_FILTER);
+ when(serviceWithFilter.getPollingLoopPatternFilters()).thenReturn(List.of());
+ when(serviceWithFilter.getShouldAutoTransact(anyString())).thenReturn(true);
+ when(serviceWithFilter.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(serviceWithFilter.getUid()).thenReturn(USER_ID);
+ ApduServiceInfo overlappingServiceWithFilter = mock(ApduServiceInfo.class);
+ when(overlappingServiceWithFilter.getPollingLoopFilters()).thenReturn(POLLING_LOOP_FILTER);
+ when(overlappingServiceWithFilter.getPollingLoopPatternFilters()).thenReturn(List.of());
+ ApduServiceInfo serviceWithPatternFilter = mock(ApduServiceInfo.class);
+ when(serviceWithPatternFilter.getPollingLoopFilters()).thenReturn(List.of());
+ when(serviceWithPatternFilter.getPollingLoopPatternFilters())
+ .thenReturn(POLLING_LOOP_PATTEN_FILTER);
+ when(mRegisteredAidCache.resolvePollingLoopFilterConflict(anyList()))
+ .thenReturn(serviceWithFilter);
+ mHostEmulationManager.updatePollingLoopFilters(USER_ID, List.of(serviceWithFilter,
+ serviceWithPatternFilter, overlappingServiceWithFilter));
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = USER_ID;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+ when(mPackageManager.getApplicationInfo(eq(WALLET_HOLDER_PACKAGE_NAME), eq(0)))
+ .thenReturn(applicationInfo);
+ String data = "filter";
+ PollingFrame frame1 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_UNKNOWN,
+ data.getBytes(), 0, 0, false);
+ PollingFrame frame2 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_OFF,
+ null, 0, 0, false);
+
+ mHostEmulationManager.mActiveService = mMessanger;
+
+ mHostEmulationManager.onPollingLoopDetected(List.of(frame1, frame2));
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mRegisteredAidCache)
+ .resolvePollingLoopFilterConflict(mServiceListArgumentCaptor.capture());
+ Assert.assertTrue(mServiceListArgumentCaptor.getValue().contains(serviceWithFilter));
+ Assert.assertTrue(mServiceListArgumentCaptor.getValue()
+ .contains(overlappingServiceWithFilter));
+ verify(mNfcAdapter).setObserveModeEnabled(eq(false));
+ Assert.assertTrue(mHostEmulationManager.mEnableObserveModeAfterTransaction);
+ Assert.assertTrue(frame1.getTriggeredAutoTransact());
+ Assert.assertEquals(mHostEmulationManager.mState, HostEmulationManager.STATE_POLLING_LOOP);
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Bundle bundle = message.getData();
+ Assert.assertEquals(message.what, HostApduService.MSG_POLLING_LOOP);
+ Assert.assertTrue(bundle.containsKey(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE));
+ ArrayList<PollingFrame> sentFrames = bundle
+ .getParcelableArrayList(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE);
+ Assert.assertTrue(sentFrames.contains(frame1));
+ Assert.assertTrue(sentFrames.contains(frame2));
+ Assert.assertNull(mHostEmulationManager.mPendingPollingLoopFrames);
+ }
+
+ @Test
+ public void testOnPollingLoopDetected_paymentServiceAlreadyBound_4Frames()
+ throws PackageManager.NameNotFoundException, RemoteException {
+ ApduServiceInfo serviceWithFilter = mock(ApduServiceInfo.class);
+ when(serviceWithFilter.getPollingLoopFilters()).thenReturn(POLLING_LOOP_FILTER);
+ when(serviceWithFilter.getPollingLoopPatternFilters()).thenReturn(List.of());
+ when(serviceWithFilter.getShouldAutoTransact(anyString())).thenReturn(true);
+ when(serviceWithFilter.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(serviceWithFilter.getUid()).thenReturn(USER_ID);
+ mHostEmulationManager.updatePollingLoopFilters(USER_ID, List.of(serviceWithFilter));
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mRegisteredAidCache.getPreferredService()).thenReturn(WALLET_PAYMENT_SERVICE);
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = USER_ID;
+ when(mPackageManager.getApplicationInfo(eq(WALLET_HOLDER_PACKAGE_NAME), eq(0)))
+ .thenReturn(applicationInfo);
+ PollingFrame frame1 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_ON,
+ null, 0, 0, false);
+ PollingFrame frame2 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_ON,
+ null, 0, 0, false);
+ PollingFrame frame3 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_OFF,
+ null, 0, 0, false);
+ PollingFrame frame4 = new PollingFrame(PollingFrame.POLLING_LOOP_TYPE_OFF,
+ null, 0, 0, false);
+ mHostEmulationManager.mPaymentService = mMessanger;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+
+ mHostEmulationManager.onPollingLoopDetected(List.of(frame1, frame2, frame3, frame4));
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Bundle bundle = message.getData();
+ Assert.assertEquals(message.what, HostApduService.MSG_POLLING_LOOP);
+ Assert.assertTrue(bundle.containsKey(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE));
+ ArrayList<PollingFrame> sentFrames = bundle
+ .getParcelableArrayList(HostApduService.KEY_POLLING_LOOP_FRAMES_BUNDLE);
+ Assert.assertTrue(sentFrames.contains(frame1));
+ Assert.assertTrue(sentFrames.contains(frame2));
+ Assert.assertTrue(sentFrames.contains(frame3));
+ Assert.assertTrue(sentFrames.contains(frame4));
+ Assert.assertNull(mHostEmulationManager.mPendingPollingLoopFrames);
+ }
+
+ @Test
+ public void testOnPreferredForegroundServiceChanged_noPreviouslyBoundService() {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ UserHandle userHandle = UserHandle.of(USER_ID);
+
+ mHostEmulationManager.onPreferredForegroundServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(userHandle));
+ verifyNoMoreInteractions(mContext);
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ Assert.assertEquals(mHostEmulationManager.mServiceUserId, USER_ID);
+ }
+
+ @Test
+ public void testOnPreferredForegroundServiceChanged_nullService() {
+ mHostEmulationManager.mServiceBound = true;
+
+ mHostEmulationManager.onPreferredForegroundServiceChanged(USER_ID, null);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).unbindService(eq(mHostEmulationManager.getServiceConnection()));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnPreferredForegroundServiceChanged_nullService_previouslyBoundService() {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ UserHandle userHandle = UserHandle.of(USER_ID);
+ mHostEmulationManager.mServiceBound = true;
+
+ mHostEmulationManager.onPreferredForegroundServiceChanged(USER_ID, WALLET_PAYMENT_SERVICE);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).unbindService(eq(mHostEmulationManager.getServiceConnection()));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(userHandle));
+ verifyNoMoreInteractions(mContext);
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ Assert.assertEquals(mHostEmulationManager.mServiceUserId, USER_ID);
+ Assert.assertNotNull(mServiceConnectionArgumentCaptor.getValue());
+ }
+
+ @Test
+ public void testOnHostEmulationActivated() {
+ mHostEmulationManager.onHostEmulationActivated();
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).sendBroadcastAsUser(mIntentArgumentCaptor.capture(),
+ eq(UserHandle.ALL));
+ verifyNoMoreInteractions(mContext);
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(TapAgainDialog.ACTION_CLOSE, intent.getAction());
+ Assert.assertEquals(HostEmulationManager.NFC_PACKAGE, intent.getPackage());
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SELECT, mHostEmulationManager.getState());
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateIdle() {
+ byte[] emptyData = new byte[1];
+ mHostEmulationManager.mState = HostEmulationManager.STATE_IDLE;
+
+ mHostEmulationManager.onHostEmulationData(emptyData);
+
+ verifyZeroInteractions(mNfcService);
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Deactivate() {
+ byte[] emptyData = new byte[1];
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_DEACTIVATE;
+
+ mHostEmulationManager.onHostEmulationData(emptyData);
+
+ verifyZeroInteractions(mNfcService);
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_hceAid() {
+ byte[] hceAidData = createSelectAidData(HostEmulationManager.ANDROID_HCE_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+
+ mHostEmulationManager.onHostEmulationData(hceAidData);
+
+ verify(mNfcService).sendData(eq(HostEmulationManager.ANDROID_HCE_RESPONSE));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_nullResolveInfo() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(null);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_emptyResolveInfoServices() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_defaultServiceExists_requiresUnlock() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(true);
+ when(apduServiceInfo.getUid()).thenReturn(USER_ID);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ aidResolveInfo.defaultService = apduServiceInfo;
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(apduServiceInfo, times(2)).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).logCardEmulationWrongSettingEvent();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendRequireUnlockIntent();
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyTapAgainLaunched(apduServiceInfo, CardEmulation.CATEGORY_PAYMENT);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_defaultServiceExists_secureNfcEnabled() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ aidResolveInfo.defaultService = apduServiceInfo;
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(apduServiceInfo, times(2)).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).logCardEmulationWrongSettingEvent();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendRequireUnlockIntent();
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyTapAgainLaunched(apduServiceInfo, CardEmulation.CATEGORY_PAYMENT);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_defaultServiceExists_requiresScreenOn() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(true);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(false);
+ aidResolveInfo.defaultService = apduServiceInfo;
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(apduServiceInfo).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).logCardEmulationWrongSettingEvent();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_defaultServiceExists_notOnHost() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
+ when(apduServiceInfo.isOnHost()).thenReturn(false);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ aidResolveInfo.defaultService = apduServiceInfo;
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ verify(apduServiceInfo).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).logCardEmulationNoRoutingEvent();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mNfcService).sendData(eq(HostEmulationManager.AID_NOT_FOUND));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_noDefaultService_noActiveService() {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+ when(apduServiceInfo.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ ExtendedMockito.verify(() -> {
+ NfcStatsLog.write(NfcStatsLog.NFC_AID_CONFLICT_OCCURRED, MOCK_AID);
+ });
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_OTHER));
+ verify(mStatsUtils).logCardEmulationWrongSettingEvent();
+ Assert.assertEquals(HostEmulationManager.STATE_W4_DEACTIVATE,
+ mHostEmulationManager.getState());
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyResolverLaunched((ArrayList)aidResolveInfo.services, null,
+ CardEmulation.CATEGORY_PAYMENT);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_noDefaultService_matchingActiveService()
+ throws RemoteException {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ IBinder binder = mock(IBinder.class);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
+ when(apduServiceInfo.isOnHost()).thenReturn(false);
+ when(apduServiceInfo.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+ mHostEmulationManager.mActiveServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mActiveService = mMessanger;
+ when(mMessanger.getBinder()).thenReturn(binder);
+ mHostEmulationManager.mPaymentServiceBound = true;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mPaymentService = mMessanger;
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ Assert.assertEquals(HostEmulationManager.STATE_XFER, mHostEmulationManager.getState());
+ verify(apduServiceInfo).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).notifyCardEmulationEventWaitingForResponse();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mContext);
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Bundle bundle = message.getData();
+ Assert.assertEquals(message.what, HostApduService.MSG_COMMAND_APDU);
+ Assert.assertTrue(bundle.containsKey(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(mockAidData, bundle.getByteArray(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(mHostEmulationManager.getLocalMessenger(), message.replyTo);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_noDefaultService_noBoundActiveService() {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
+ when(apduServiceInfo.isOnHost()).thenReturn(false);
+ when(apduServiceInfo.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(apduServiceInfo.getUid()).thenReturn(USER_ID);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+ mHostEmulationManager.mActiveServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mPaymentServiceBound = false;
+ mHostEmulationManager.mServiceBound = false;
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SERVICE,
+ mHostEmulationManager.getState());
+ Assert.assertEquals(mockAidData, mHostEmulationManager.mSelectApdu);
+ verify(apduServiceInfo).getUid();
+ verify(mStatsUtils).setCardEmulationEventCategory(eq(CardEmulation.CATEGORY_PAYMENT));
+ verify(mStatsUtils).setCardEmulationEventUid(eq(USER_ID));
+ verify(mStatsUtils).notifyCardEmulationEventWaitingForResponse();
+ verify(mRegisteredAidCache).resolveAid(eq(MOCK_AID));
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(USER_HANDLE));
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(mHostEmulationManager.getServiceConnection(),
+ mServiceConnectionArgumentCaptor.getValue());
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ Assert.assertEquals(USER_ID, mHostEmulationManager.mServiceUserId);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Select_noSelectAid() {
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ mHostEmulationManager.mPaymentServiceBound = true;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mPaymentService = mMessanger;
+
+ mHostEmulationManager.onHostEmulationData(null);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mNfcService).sendData(eq(HostEmulationManager.UNKNOWN_ERROR));
+ verifyNoMoreInteractions(mNfcService);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateW4Service() {
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SERVICE;
+
+ mHostEmulationManager.onHostEmulationData(null);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyNoMoreInteractions(mNfcService);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateXfer_nullAid_activeService() throws RemoteException {
+ byte[] data = new byte[3];
+ mHostEmulationManager.mState = HostEmulationManager.STATE_XFER;
+ mHostEmulationManager.mActiveServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mActiveService = mMessanger;
+
+ mHostEmulationManager.onHostEmulationData(data);
+
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Bundle bundle = message.getData();
+ Assert.assertEquals(message.what, HostApduService.MSG_COMMAND_APDU);
+ Assert.assertTrue(bundle.containsKey(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(data, bundle.getByteArray(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(mHostEmulationManager.getLocalMessenger(), message.replyTo);
+ verifyNoMoreInteractions(mNfcService);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateXfer_selectAid_activeService() throws RemoteException {
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ IBinder binder = mock(IBinder.class);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_XFER;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
+ when(apduServiceInfo.isOnHost()).thenReturn(false);
+ when(apduServiceInfo.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+ mHostEmulationManager.mActiveServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mActiveService = mMessanger;
+ when(mMessanger.getBinder()).thenReturn(binder);
+ mHostEmulationManager.mPaymentServiceBound = true;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mPaymentService = mMessanger;
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ Assert.assertEquals(HostEmulationManager.STATE_XFER, mHostEmulationManager.getState());
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Bundle bundle = message.getData();
+ Assert.assertEquals(message.what, HostApduService.MSG_COMMAND_APDU);
+ Assert.assertTrue(bundle.containsKey(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(mockAidData, bundle.getByteArray(HostEmulationManager.DATA_KEY));
+ Assert.assertEquals(mHostEmulationManager.getLocalMessenger(), message.replyTo);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationData_stateXfer_selectAid_noActiveService() throws RemoteException {
+ when(mContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+ byte[] mockAidData = createSelectAidData(MOCK_AID);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_XFER;
+ ApduServiceInfo apduServiceInfo = mock(ApduServiceInfo.class);
+ RegisteredAidCache.AidResolveInfo aidResolveInfo = mRegisteredAidCache.new AidResolveInfo();
+ aidResolveInfo.services = new ArrayList<>();
+ aidResolveInfo.services.add(apduServiceInfo);
+ aidResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ when(apduServiceInfo.requiresUnlock()).thenReturn(false);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(false);
+ when(apduServiceInfo.isOnHost()).thenReturn(false);
+ when(apduServiceInfo.getComponent()).thenReturn(WALLET_PAYMENT_SERVICE);
+ when(apduServiceInfo.getUid()).thenReturn(USER_ID);
+ when(mNfcService.isSecureNfcEnabled()).thenReturn(false);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mRegisteredAidCache.resolveAid(eq(MOCK_AID))).thenReturn(aidResolveInfo);
+ mHostEmulationManager.mActiveServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mPaymentServiceBound = false;
+ mHostEmulationManager.mServiceBound = false;
+
+ mHostEmulationManager.onHostEmulationData(mockAidData);
+
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SERVICE,
+ mHostEmulationManager.getState());
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).bindServiceAsUser(mIntentArgumentCaptor.capture(),
+ mServiceConnectionArgumentCaptor.capture(),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+ eq(USER_HANDLE));
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(intent.getAction(), HostApduService.SERVICE_INTERFACE);
+ Assert.assertEquals(intent.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(mHostEmulationManager.getServiceConnection(),
+ mServiceConnectionArgumentCaptor.getValue());
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ Assert.assertEquals(USER_ID, mHostEmulationManager.mServiceUserId);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnHostEmulationDeactivated_activeService_enableObserveModeAfterTransaction()
+ throws RemoteException {
+ mHostEmulationManager.mActiveService = mMessanger;
+ mHostEmulationManager.mServiceBound = true;
+ mHostEmulationManager.mServiceUserId = USER_ID;
+ mHostEmulationManager.mServiceName = WALLET_PAYMENT_SERVICE;
+ mHostEmulationManager.mEnableObserveModeAfterTransaction = true;
+
+ mHostEmulationManager.onHostEmulationDeactivated();
+
+ Assert.assertNull(mHostEmulationManager.mActiveService);
+ Assert.assertNull(mHostEmulationManager.mActiveServiceName);
+ Assert.assertNull(mHostEmulationManager.mServiceName);
+ Assert.assertNull(mHostEmulationManager.mService);
+ Assert.assertNull(mHostEmulationManager.mPendingPollingLoopFrames);
+ Assert.assertEquals(-1, mHostEmulationManager.mActiveServiceUserId);
+ Assert.assertEquals(-1, mHostEmulationManager.mServiceUserId);
+ Assert.assertEquals(HostEmulationManager.STATE_IDLE, mHostEmulationManager.getState());
+ Assert.assertFalse(mHostEmulationManager.mEnableObserveModeAfterTransaction);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ verify(mNfcAdapter).setObserveModeEnabled(eq(true));
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Assert.assertEquals(message.what, HostApduService.MSG_DEACTIVATED);
+ Assert.assertEquals(message.arg1, HostApduService.DEACTIVATION_LINK_LOSS);
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).unbindService(mServiceConnectionArgumentCaptor.capture());
+ verifyNoMoreInteractions(mMessanger);
+ Assert.assertEquals(mHostEmulationManager.getServiceConnection(),
+ mServiceConnectionArgumentCaptor.getValue());
+ verifyNoMoreInteractions(mContext);
+ verify(mStatsUtils).logCardEmulationDeactivatedEvent();
+ }
+
+ @Test
+ public void testOnHostEmulationDeactivated_noActiveService() throws RemoteException {
+ mHostEmulationManager.mActiveService = null;
+ mHostEmulationManager.mServiceBound = false;
+ mHostEmulationManager.mServiceUserId = USER_ID;
+ mHostEmulationManager.mEnableObserveModeAfterTransaction = false;
+
+ mHostEmulationManager.onHostEmulationDeactivated();
+
+ Assert.assertNull(mHostEmulationManager.mActiveService);
+ Assert.assertNull(mHostEmulationManager.mActiveServiceName);
+ Assert.assertNull(mHostEmulationManager.mServiceName);
+ Assert.assertNull(mHostEmulationManager.mService);
+ Assert.assertNull(mHostEmulationManager.mPendingPollingLoopFrames);
+ Assert.assertEquals(-1, mHostEmulationManager.mActiveServiceUserId);
+ Assert.assertEquals(-1, mHostEmulationManager.mServiceUserId);
+ Assert.assertEquals(HostEmulationManager.STATE_IDLE, mHostEmulationManager.getState());
+ Assert.assertFalse(mHostEmulationManager.mEnableObserveModeAfterTransaction);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verifyZeroInteractions(mMessanger);
+ verifyNoMoreInteractions(mMessanger);
+ verifyNoMoreInteractions(mContext);
+ verify(mStatsUtils).logCardEmulationDeactivatedEvent();
+ }
+
+ @Test
+ public void testOnOffHostAidSelected_noActiveService_stateXfer() {
+ mHostEmulationManager.mActiveService = null;
+ mHostEmulationManager.mServiceBound = false;
+ mHostEmulationManager.mState = HostEmulationManager.STATE_XFER;
+
+ mHostEmulationManager.onOffHostAidSelected();
+
+ Assert.assertNull(mHostEmulationManager.mActiveService);
+ Assert.assertNull(mHostEmulationManager.mActiveServiceName);
+ Assert.assertEquals(-1, mHostEmulationManager.mActiveServiceUserId);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).sendBroadcastAsUser(mIntentArgumentCaptor.capture(), eq(UserHandle.ALL));
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SELECT,
+ mHostEmulationManager.getState());
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(TapAgainDialog.ACTION_CLOSE, intent.getAction());
+ Assert.assertEquals(HostEmulationManager.NFC_PACKAGE, intent.getPackage());
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnOffHostAidSelected_activeServiceBound_stateXfer() throws RemoteException {
+ mHostEmulationManager.mActiveService = mMessanger;
+ mHostEmulationManager.mServiceBound = true;
+ mHostEmulationManager.mState = HostEmulationManager.STATE_XFER;
+
+ mHostEmulationManager.onOffHostAidSelected();
+
+ Assert.assertNull(mHostEmulationManager.mActiveService);
+ Assert.assertNull(mHostEmulationManager.mActiveServiceName);
+ Assert.assertEquals(-1, mHostEmulationManager.mActiveServiceUserId);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ verify(mContext).unbindService(mServiceConnectionArgumentCaptor.capture());
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).sendBroadcastAsUser(mIntentArgumentCaptor.capture(), eq(UserHandle.ALL));
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SELECT,
+ mHostEmulationManager.getState());
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(TapAgainDialog.ACTION_CLOSE, intent.getAction());
+ Assert.assertEquals(HostEmulationManager.NFC_PACKAGE, intent.getPackage());
+ verify(mMessanger).send(mMessageArgumentCaptor.capture());
+ Message message = mMessageArgumentCaptor.getValue();
+ Assert.assertEquals(message.what, HostApduService.MSG_DEACTIVATED);
+ Assert.assertEquals(message.arg1, HostApduService.DEACTIVATION_DESELECTED);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testOnOffHostAidSelected_activeServiceBound_stateNonXfer() throws RemoteException {
+ mHostEmulationManager.mActiveService = mMessanger;
+ mHostEmulationManager.mServiceBound = true;
+ mHostEmulationManager.mState = HostEmulationManager.STATE_IDLE;
+
+ mHostEmulationManager.onOffHostAidSelected();
+
+ Assert.assertNull(mHostEmulationManager.mActiveService);
+ Assert.assertNull(mHostEmulationManager.mActiveServiceName);
+ Assert.assertEquals(-1, mHostEmulationManager.mActiveServiceUserId);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ verify(mContext).unbindService(mServiceConnectionArgumentCaptor.capture());
+ verify(mContext).getSystemService(eq(PowerManager.class));
+ verify(mContext).getSystemService(eq(KeyguardManager.class));
+ verify(mContext).sendBroadcastAsUser(mIntentArgumentCaptor.capture(), eq(UserHandle.ALL));
+ Assert.assertEquals(HostEmulationManager.STATE_W4_SELECT,
+ mHostEmulationManager.getState());
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(TapAgainDialog.ACTION_CLOSE, intent.getAction());
+ Assert.assertEquals(HostEmulationManager.NFC_PACKAGE, intent.getPackage());
+ verifyZeroInteractions(mMessanger);
+ verifyNoMoreInteractions(mContext);
+ }
+
+ @Test
+ public void testServiceConnectionOnServiceConnected_stateSelectW4_selectApdu()
+ throws RemoteException {
+ IBinder service = mock(IBinder.class);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ mHostEmulationManager.mSelectApdu = new byte[3];
+
+ mHostEmulationManager.getServiceConnection().onServiceConnected(WALLET_PAYMENT_SERVICE,
+ service);
+
+ Assert.assertEquals(mHostEmulationManager.mServiceName, WALLET_PAYMENT_SERVICE);
+ Assert.assertNotNull(mHostEmulationManager.mService);
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ verify(mStatsUtils).notifyCardEmulationEventServiceBound();
+ Assert.assertEquals(HostEmulationManager.STATE_XFER, mHostEmulationManager.getState());
+ Assert.assertNull(mHostEmulationManager.mSelectApdu);
+ verify(service).transact(eq(1), any(), eq(null), eq(1));
+ }
+
+ @Test
+ public void testServiceConnectionOnServiceConnected_stateSelectW4_pollingLoopFrames()
+ throws RemoteException {
+ IBinder service = mock(IBinder.class);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_W4_SELECT;
+ mHostEmulationManager.mSelectApdu = null;
+ mHostEmulationManager.mPendingPollingLoopFrames = new ArrayList<>(List.of());
+
+ mHostEmulationManager.getServiceConnection().onServiceConnected(WALLET_PAYMENT_SERVICE,
+ service);
+
+ Assert.assertEquals(mHostEmulationManager.mServiceName, WALLET_PAYMENT_SERVICE);
+ Assert.assertNotNull(mHostEmulationManager.mService);
+ Assert.assertTrue(mHostEmulationManager.mServiceBound);
+ Assert.assertEquals(HostEmulationManager.STATE_XFER, mHostEmulationManager.getState());
+ Assert.assertNull(mHostEmulationManager.mPendingPollingLoopFrames);
+ verify(service).transact(eq(1), any(), eq(null), eq(1));
+ }
+
+ @Test
+ public void testServiceConnectionOnServiceConnected_stateIdle() {
+ IBinder service = mock(IBinder.class);
+ mHostEmulationManager.mState = HostEmulationManager.STATE_IDLE;
+
+ mHostEmulationManager.getServiceConnection().onServiceConnected(WALLET_PAYMENT_SERVICE,
+ service);
+
+ verifyZeroInteractions(service);
+ }
+
+ @Test
+ public void testServiceConnectionOnServiceDisconnected() {
+ mHostEmulationManager.mService = mMessanger;
+ mHostEmulationManager.mServiceBound = true;
+ mHostEmulationManager.mServiceName = WALLET_PAYMENT_SERVICE;
+
+ mHostEmulationManager.getServiceConnection().onServiceDisconnected(WALLET_PAYMENT_SERVICE);
+
+ Assert.assertNull(mHostEmulationManager.mService);
+ Assert.assertFalse(mHostEmulationManager.mServiceBound);
+ Assert.assertNull(mHostEmulationManager.mServiceName);
+ }
+
+ @Test
+ public void testPaymentServiceConnectionOnServiceConnected() {
+ IBinder service = mock(IBinder.class);
+ mHostEmulationManager.mLastBoundPaymentServiceName = WALLET_PAYMENT_SERVICE;
+
+ mHostEmulationManager.getPaymentConnection().onServiceConnected(WALLET_PAYMENT_SERVICE,
+ service);
+
+ Assert.assertNotNull(mHostEmulationManager.mPaymentServiceName);
+ Assert.assertEquals(WALLET_PAYMENT_SERVICE, mHostEmulationManager.mPaymentServiceName);
+ }
+
+ @Test
+ public void testPaymentServiceConnectionOnServiceDisconnected() {
+ mHostEmulationManager.mPaymentService = mMessanger;
+ mHostEmulationManager.mPaymentServiceBound = true;
+ mHostEmulationManager.mPaymentServiceName = WALLET_PAYMENT_SERVICE;
+
+ mHostEmulationManager.getPaymentConnection().onServiceDisconnected(WALLET_PAYMENT_SERVICE);
+
+ Assert.assertNull(mHostEmulationManager.mPaymentService);
+ Assert.assertNull(mHostEmulationManager.mPaymentServiceName);
+ }
+
+ @Test
+ public void testFindSelectAid_properAid() {
+ final byte[] aidData = createSelectAidData(MOCK_AID);
+
+ String aidString = mHostEmulationManager.findSelectAid(aidData);
+
+ Assert.assertEquals(MOCK_AID, aidString);
+ }
+
+ @Test
+ public void testFindSelectAid_nullData() {
+ String aidString = mHostEmulationManager.findSelectAid(null);
+
+ Assert.assertNull(aidString);
+ }
+
+ @Test
+ public void testFindSelectAid_shortLength() {
+ final byte[] aidData = new byte[1];
+
+ String aidString = mHostEmulationManager.findSelectAid(aidData);
+
+ Assert.assertNull(aidString);
+ }
+
+ @Test
+ public void testFindSelectAid_longAidLength() {
+ final byte[] aidData = createSelectAidData(MOCK_AID);
+ aidData[4] = (byte)(HostEmulationManager.SELECT_APDU_HDR_LENGTH
+ + HexFormat.of().parseHex(MOCK_AID).length + 1);
+
+ String aidString = mHostEmulationManager.findSelectAid(aidData);
+
+ Assert.assertNull(aidString);
+ }
+
+ private void verifyTapAgainLaunched(ApduServiceInfo service, String category) {
+ verify(mContext).getPackageName();
+ verify(mContext).startActivityAsUser(mIntentArgumentCaptor.capture(), eq(USER_HANDLE));
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(category, intent.getStringExtra(TapAgainDialog.EXTRA_CATEGORY));
+ Assert.assertEquals(service, intent.getParcelableExtra(TapAgainDialog.EXTRA_APDU_SERVICE));
+ int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+ Assert.assertEquals(flags, intent.getFlags());
+ Assert.assertEquals(TapAgainDialog.class.getCanonicalName(),
+ intent.getComponent().getClassName());
+ }
+
+ private void verifyResolverLaunched(ArrayList<ApduServiceInfo> services,
+ ComponentName failedComponent, String category) {
+ verify(mContext).getPackageName();
+ verify(mContext).startActivityAsUser(mIntentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
+ Intent intent = mIntentArgumentCaptor.getValue();
+ Assert.assertEquals(category, intent.getStringExtra(AppChooserActivity.EXTRA_CATEGORY));
+ Assert.assertEquals(services,
+ intent.getParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES));
+ if (failedComponent != null) {
+ Assert.assertEquals(failedComponent,
+ intent.getParcelableExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT));
+ }
+ int flags = Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK;
+ Assert.assertEquals(flags, intent.getFlags());
+ Assert.assertEquals(AppChooserActivity.class.getCanonicalName(),
+ intent.getComponent().getClassName());
+ }
+
+ private static byte[] createSelectAidData(String aid) {
+ byte[] aidStringData = HexFormat.of().parseHex(aid);
+ byte[] aidData = new byte[HostEmulationManager.SELECT_APDU_HDR_LENGTH
+ + aidStringData.length];
+ aidData[0] = 0x00;
+ aidData[1] = HostEmulationManager.INSTR_SELECT;
+ aidData[2] = 0x04;
+ aidData[3] = 0x00;
+ aidData[4] = (byte)aidStringData.length;
+ System.arraycopy(aidStringData, 0, aidData, HostEmulationManager.SELECT_APDU_HDR_LENGTH,
+ aidData.length - HostEmulationManager.SELECT_APDU_HDR_LENGTH);
+ return aidData;
+ }
+
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/HostNfcFEmulationManagerTest.java b/tests/unit/src/com/android/nfc/cardemulation/HostNfcFEmulationManagerTest.java
new file mode 100644
index 0000000..7d29c74
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/HostNfcFEmulationManagerTest.java
@@ -0,0 +1,541 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_IDLE;
+import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_W4_SERVICE;
+import static com.android.nfc.cardemulation.HostNfcFEmulationManager.STATE_XFER;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.nfc.cardemulation.HostNfcFService;
+import android.nfc.cardemulation.NfcFServiceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.UserHandle;
+import android.util.proto.ProtoOutputStream;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.nfc.NfcService;
+
+import java.io.PrintWriter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+
+@RunWith(AndroidJUnit4.class)
+public class HostNfcFEmulationManagerTest {
+
+ private HostNfcFEmulationManager manager;
+
+ @Mock
+ private RegisteredT3tIdentifiersCache mockIdentifiersCache;
+ @Mock
+ private Messenger mockActiveService;
+ @Mock
+ private Context mockContext;
+ @Mock
+ private IBinder mockIBinder;
+ @Mock
+ private PrintWriter mockPrintWriter;
+ @Mock
+ private ProtoOutputStream mockProto;
+ @Mock
+ private NfcFServiceInfo mockResolvedService;
+
+ @Captor
+ ArgumentCaptor<Message> msgCaptor;
+ @Captor
+ ArgumentCaptor<Intent> intentCaptor;
+ @Captor
+ ArgumentCaptor<UserHandle> userHandleCaptor;
+ @Captor
+ ArgumentCaptor<ServiceConnection> serviceConnectionCaptor;
+ @Captor
+ ArgumentCaptor<Integer> flagsCaptor;
+
+ private static final ComponentName testService = new ComponentName(
+ "com.android.test.walletroleholder",
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+ private static final byte[] DATA = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+ private static final int USER_ID = 2;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mockIdentifiersCache.resolveNfcid2(anyString())).thenReturn(null);
+ when(mockActiveService.getBinder()).thenReturn(mockIBinder);
+ when(mockIdentifiersCache.resolveNfcid2(anyString())).thenReturn(mockResolvedService);
+ when(mockResolvedService.getUid()).thenReturn(USER_ID);
+ doNothing().when(mockPrintWriter).println(anyString());
+ doNothing().when(mockProto).write(anyLong(), anyString());
+ doNothing().when(mockProto).end(anyLong());
+ }
+
+ @Test
+ public void testConstructor() {
+ Looper.prepare();
+
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ assertThat(manager.mContext).isEqualTo(mockContext);
+ assertThat(manager.mLock).isNotNull();
+ assertThat(manager.mEnabledFgServiceName).isNull();
+ assertThat(manager.mT3tIdentifiersCache).isEqualTo(mockIdentifiersCache);
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testOnEnabledForegroundNfcFServiceChangedWithNonNullService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ manager.onEnabledForegroundNfcFServiceChanged(USER_ID, testService);
+
+ assertThat(manager.mEnabledFgServiceUserId).isEqualTo(USER_ID);
+ assertThat(manager.mEnabledFgServiceName).isEqualTo(testService);
+ }
+
+ @Test
+ public void testOnEnabledForegroundNfcFServiceChangedWithNullService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+ manager.mServiceBound = true;
+
+ manager.onEnabledForegroundNfcFServiceChanged(USER_ID, /* service = */ null);
+
+ verify(mockActiveService).send(any(Message.class));
+ verify(mockContext).unbindService(any(ServiceConnection.class));
+ assertThat(manager.mEnabledFgServiceUserId).isEqualTo(USER_ID);
+ assertThat(manager.mEnabledFgServiceName).isNull();
+ assertThat(manager.mServiceBound).isFalse();
+ assertThat(manager.mService).isNull();
+ assertThat(manager.mServiceName).isNull();
+ assertThat(manager.mServiceUserId).isEqualTo(-1);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithNullDataAndNoActiveService_ReturnsEarly()
+ throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = null;
+
+ manager.onHostEmulationData(/* data = */ null);
+
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithDisabledResolvedServiceFromActiveService_ReturnsEarly()
+ throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = null;
+
+ manager.onHostEmulationData(/* data = */ null);
+
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithEnabledResolvedServiceFromCache_BindToService()
+ throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = testService;
+ manager.mServiceBound = true;
+ manager.mServiceName = testService;
+ manager.mServiceUserId = 0;
+ manager.mService = mockActiveService;
+
+ manager.onHostEmulationData(DATA);
+
+ verify(mockActiveService).send(any(Message.class));
+ assertThat(manager.mState).isEqualTo(STATE_XFER);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithValidExistingService_BindToService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = testService;
+ manager.mEnabledFgServiceUserId = USER_ID;
+ manager.mServiceBound = true;
+ manager.mServiceName = testService;
+ manager.mServiceUserId = USER_ID;
+ manager.mService = mockActiveService;
+
+ manager.onHostEmulationData(/* data = */ null);
+
+ verify(mockActiveService).send(any(Message.class));
+ assertThat(manager.mState).isEqualTo(STATE_XFER);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithInvalidExistingService_WaitingForNewService()
+ throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = testService;
+
+ manager.onHostEmulationData(DATA);
+
+ assertThat(manager.mState).isEqualTo(STATE_W4_SERVICE);
+ assertThat(manager.mPendingPacket).isEqualTo(DATA);
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithXFERState_SendRegularPacketData() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = testService;
+ manager.mState = STATE_XFER;
+ manager.mActiveService = mockActiveService;
+
+ manager.onHostEmulationData(DATA);
+
+ verify(mockActiveService).send(any(Message.class));
+ }
+
+ @Test
+ public void testOnHostEmulationDataWithW4ServiceState_DoNothing() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveServiceName = testService;
+ manager.mEnabledFgServiceName = testService;
+ manager.mState = STATE_W4_SERVICE;
+
+ manager.onHostEmulationData(DATA);
+
+ assertThat(manager.mState).isEqualTo(STATE_W4_SERVICE);
+ }
+
+ @Test
+ public void testOnHostEmulationDeactivated() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+
+ manager.onHostEmulationDeactivated();
+
+ verify(mockActiveService).send(any(Message.class));
+ assertThat(manager.mActiveService).isNull();
+ assertThat(manager.mActiveServiceName).isNull();
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testOnNfcDisabled() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+
+ manager.onNfcDisabled();
+
+ verify(mockActiveService).send(any(Message.class));
+ assertThat(manager.mEnabledFgServiceName).isNull();
+ assertThat(manager.mActiveService).isNull();
+ assertThat(manager.mActiveServiceName).isNull();
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testOnUserSwitched() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+
+ manager.onUserSwitched();
+
+ verify(mockActiveService).send(any(Message.class));
+ assertThat(manager.mEnabledFgServiceName).isNull();
+ assertThat(manager.mActiveService).isNull();
+ assertThat(manager.mActiveServiceName).isNull();
+ assertThat(manager.mState).isEqualTo(STATE_IDLE);
+ }
+
+ @Test
+ public void testSendDataToServiceLockedWithNewService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ manager.sendDataToServiceLocked(mockActiveService, DATA);
+
+ verify(mockActiveService).send(msgCaptor.capture());
+ Message msg = msgCaptor.getValue();
+ assertThat(msg.getTarget()).isNull();
+ assertThat(msg.what).isEqualTo(HostNfcFService.MSG_COMMAND_PACKET);
+ assertThat(msg.getData().getByteArray("data")).isEqualTo(DATA);
+ }
+
+ @Test
+ public void testSendDataToServiceLockedWithExistingService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+
+ manager.sendDataToServiceLocked(mockActiveService, DATA);
+
+ verify(mockActiveService).send(msgCaptor.capture());
+ Message msg = msgCaptor.getValue();
+ assertThat(msg.getTarget()).isNull();
+ assertThat(msg.what).isEqualTo(HostNfcFService.MSG_COMMAND_PACKET);
+ assertThat(msg.getData().getByteArray("data")).isEqualTo(DATA);
+ }
+
+ @Test
+ public void testDeactivateToActiveServiceLockedWithNullActiveService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ manager.sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
+
+ verify(mockActiveService, times(0)).send(any(Message.class));
+ }
+
+ @Test
+ public void testDeactivateToActiveServiceLockedWithNonNullActiveService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+
+ manager.sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
+
+ verify(mockActiveService).send(msgCaptor.capture());
+ Message msg = msgCaptor.getValue();
+ assertThat(msg.getTarget()).isNull();
+ assertThat(msg.what).isEqualTo(HostNfcFService.MSG_DEACTIVATED);
+ assertThat(msg.arg1).isEqualTo(HostNfcFService.DEACTIVATION_LINK_LOSS);
+ }
+
+ @Test
+ public void testBindServiceWithNewService_SuccessfullyBindsToService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ when(mockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+ any(UserHandle.class))).thenReturn(true);
+
+ Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService);
+
+ verify(mockContext).bindServiceAsUser(intentCaptor.capture(), serviceConnectionCaptor.capture(),
+ flagsCaptor.capture(), userHandleCaptor.capture());
+ Intent bindIntent = intentCaptor.getValue();
+ assertThat(bindIntent.getAction()).isEqualTo(HostNfcFService.SERVICE_INTERFACE);
+ assertThat(bindIntent.getComponent()).isEqualTo(testService);
+ assertThat(serviceConnectionCaptor.getValue()).isNotNull();
+ assertThat(flagsCaptor.getValue()).isEqualTo(Context.BIND_AUTO_CREATE);
+ assertThat(userHandleCaptor.getValue()).isEqualTo(UserHandle.of(USER_ID));
+ assertThat(manager.mServiceUserId).isEqualTo(USER_ID);
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testBindServiceWithNewService_FailsToBindToService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ when(mockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class), anyInt(),
+ any(UserHandle.class))).thenReturn(false);
+
+ Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService);
+
+ assertThat(manager.mServiceUserId).isEqualTo(0);
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testBindServiceWithExistingService_ServiceAlreadyBound() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mService = mockActiveService;
+ manager.mServiceBound = true;
+ manager.mServiceName = testService;
+ manager.mServiceUserId = USER_ID;
+
+ Messenger result = manager.bindServiceIfNeededLocked(USER_ID, testService);
+
+ assertThat(result).isEqualTo(mockActiveService);
+ }
+
+ @Test
+ public void testUnbindService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mServiceBound = true;
+
+ manager.unbindServiceIfNeededLocked();
+
+ verify(mockContext).unbindService(any(ServiceConnection.class));
+ assertThat(manager.mServiceBound).isFalse();
+ assertThat(manager.mService).isNull();
+ assertThat(manager.mServiceName).isNull();
+ assertThat(manager.mServiceUserId).isEqualTo(-1);
+ }
+
+ @Test
+ public void testFindNfcid2WithNullData() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ String result = manager.findNfcid2(/* data = */ null);
+
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testFindNfcid2WithNonNullData() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ String result = manager.findNfcid2(DATA);
+
+ assertThat(result).isEqualTo("0203040506070809");
+ }
+
+ @Test
+ public void testServiceConnectionOnConnected() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mPendingPacket = DATA;
+ manager.bindServiceIfNeededLocked(USER_ID, testService);
+ verify(mockContext).bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+ anyInt(), any(UserHandle.class));
+ ServiceConnection connection = serviceConnectionCaptor.getValue();
+
+ connection.onServiceConnected(testService, mockIBinder);
+
+ assertThat(manager.mService).isNotNull();
+ assertThat(manager.mServiceBound).isTrue();
+ assertThat(manager.mServiceName).isEqualTo(testService);
+ assertThat(manager.mState).isEqualTo(STATE_XFER);
+ assertThat(manager.mPendingPacket).isNull();
+ }
+
+ @Test
+ public void testServiceConnectionOnDisconnected() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.bindServiceIfNeededLocked(USER_ID, testService);
+ verify(mockContext).bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+ anyInt(), any(UserHandle.class));
+ ServiceConnection connection = serviceConnectionCaptor.getValue();
+
+ connection.onServiceDisconnected(testService);
+
+ assertThat(manager.mService).isNull();
+ assertThat(manager.mServiceBound).isFalse();
+ assertThat(manager.mServiceName).isNull();
+ }
+
+ @Test
+ public void testMessageHandler() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mActiveService = mockActiveService;
+ Message msg = new Message();
+ msg.replyTo = mockActiveService;
+ msg.what = HostNfcFService.MSG_RESPONSE_PACKET;
+ Bundle dataBundle = new Bundle();
+ dataBundle.putByteArray("data", new byte[]{});
+ msg.setData(dataBundle);
+ HostNfcFEmulationManager.MessageHandler messageHandler = manager.new MessageHandler();
+
+ messageHandler.handleMessage(msg);
+ }
+
+ @Test
+ public void testBytesToString() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ String result = manager.bytesToString(DATA, /* offset = */ 0, DATA.length);
+
+ assertThat(result).isEqualTo("000102030405060708090A0B");
+ }
+
+ @Test
+ public void testDumpWithBoundService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mServiceBound = true;
+
+ manager.dump(/* fd = */ null, mockPrintWriter, /* args = */ null);
+
+ verify(mockPrintWriter, times(2)).println(anyString());
+ }
+
+ @Test
+ public void testDumpWithoutBoundService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ manager.dump(/* fd = */ null, mockPrintWriter, /* args = */ null);
+
+ verify(mockPrintWriter).println(anyString());
+ }
+
+ @Test
+ public void testDumpDebugWithBoundService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+ manager.mServiceBound = true;
+ manager.mServiceName = testService;
+
+ manager.dumpDebug(mockProto);
+
+ verify(mockProto, times(2)).write(anyLong(), anyString());
+ verify(mockProto, times(1)).end(anyLong());
+ }
+
+ @Test
+ public void testDumpDebugWithoutBoundService() throws Exception {
+ Looper.prepare();
+ manager = new HostNfcFEmulationManager(mockContext, mockIdentifiersCache);
+
+ manager.dumpDebug(mockProto);
+
+ verify(mockProto, times(0)).write(anyLong(), anyString());
+ verify(mockProto, times(0)).end(anyLong());
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/nfc/cardemulation/ListAdapterTest.java b/tests/unit/src/com/android/nfc/cardemulation/ListAdapterTest.java
new file mode 100644
index 0000000..075c0dc
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/ListAdapterTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.nfc.tests.unit.R;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ListAdapterTest {
+
+ private Context context;
+ private Context mockContext;
+ private View viewInput;
+ @Mock
+ private ApduServiceInfo mockService;
+ @Mock
+ private LayoutInflater mockInflater;
+
+ @Rule
+ public final ActivityTestRule<AppChooserActivity> rule
+ = new ActivityTestRule<>(AppChooserActivity.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ // Set up mocks for onView()
+ viewInput
+ = spy(LayoutInflater.from(context).inflate(R.layout.activity_layout_test, null, false));
+ AppChooserActivity.ViewHolder viewHolder = new AppChooserActivity.ViewHolder(viewInput);
+ viewHolder.text = viewInput.findViewById(R.id.applabel);
+ viewHolder.icon = viewInput.findViewById(R.id.appicon);
+ viewHolder.banner = viewInput.findViewById(R.id.banner);
+ doReturn(viewHolder).when(viewInput).getTag();
+ when(mockInflater.inflate(anyInt(), any(), anyBoolean())).thenReturn(viewInput);
+
+ // Set up mockService
+ when(mockService.getDescription()).thenReturn("");
+ when(mockService.loadIcon(any(PackageManager.class))).thenReturn(null);
+ when(mockService.loadBanner(any(PackageManager.class))).thenReturn(null);
+ when(mockService.getUid()).thenReturn(0);
+
+ mockContext = new ContextWrapper(context) {
+ @Override
+ public Object getSystemService(String name) {
+ return mockInflater;
+ }
+ };
+ }
+
+ @Test
+ public void testListAdapterConstructor() throws Throwable {
+ rule.runOnUiThread(() -> {
+ AppChooserActivity.ListAdapter adapter
+ = rule.getActivity().new ListAdapter(mockContext, getMockServiceList());
+
+ assertThat(adapter.getCount()).isEqualTo(1);
+ assertThat(adapter.getItem(0)).isNotNull();
+ });
+ }
+
+ @Test
+ public void testListAdapterGetViewWithNullConvertView() throws Throwable {
+ rule.runOnUiThread(() -> {
+ AppChooserActivity.ListAdapter adapter
+ = rule.getActivity().new ListAdapter(mockContext, getMockServiceList());
+ View viewResult = adapter.getView(0, null, null);
+
+ assertThat(viewResult).isEqualTo(viewInput);
+ AppChooserActivity.ViewHolder viewHolderResult = (AppChooserActivity.ViewHolder) viewResult.getTag();
+ assertThat(viewHolderResult).isNotNull();
+ assertThat(viewHolderResult.icon).isNotNull();
+ assertThat(viewHolderResult.text).isNotNull();
+ });
+ }
+
+ @Test
+ public void testListAdapterGetViewWithNonNullConvertView() throws Throwable {
+ rule.runOnUiThread(() -> {
+ AppChooserActivity.ListAdapter adapter
+ = rule.getActivity().new ListAdapter(mockContext, getMockServiceList());
+ View viewResult = adapter.getView(0, viewInput, null);
+
+ assertThat(viewResult).isEqualTo(viewInput);
+ AppChooserActivity.ViewHolder viewHolderResult
+ = (AppChooserActivity.ViewHolder) viewResult.getTag();
+ assertThat(viewHolderResult).isNotNull();
+ assertThat(viewHolderResult.icon).isNotNull();
+ assertThat(viewHolderResult.text).isNotNull();
+ });
+ }
+
+ private ArrayList<ApduServiceInfo> getMockServiceList() {
+ ArrayList<ApduServiceInfo> serviceList = new ArrayList<>();
+ serviceList.add(mockService);
+ return serviceList;
+ }
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/NfcCardEmulationOccurredTest.java b/tests/unit/src/com/android/nfc/cardemulation/NfcCardEmulationOccurredTest.java
index 44d6675..110b127 100644
--- a/tests/unit/src/com/android/nfc/cardemulation/NfcCardEmulationOccurredTest.java
+++ b/tests/unit/src/com/android/nfc/cardemulation/NfcCardEmulationOccurredTest.java
@@ -37,9 +37,8 @@
import android.content.pm.PackageManager;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
-import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.PollingFrame;
import android.os.Binder;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -163,8 +162,7 @@
mStaticMockSession.finishMocking();
}
- // TODO: Remove after aosp/2902507
- // @RequiresFlagsDisabled(Flags.FLAG_STATSD_CE_EVENTS_FLAG)
+ @RequiresFlagsDisabled(Flags.FLAG_STATSD_CE_EVENTS_FLAG)
@Test
public void testHCEOther() {
if (!mNfcSupported) return;
@@ -197,35 +195,41 @@
public void testOnPollingLoopDetected() {
if (!mNfcSupported) return;
- Bundle pollingFrame = mock(Bundle.class);
+ PollingFrame pollingFrame = mock(PollingFrame.class);
+ ArrayList<PollingFrame> pollingFrames = new ArrayList<PollingFrame>();
+ pollingFrames.add(pollingFrame);
ComponentName componentName = mock(ComponentName.class);
when(componentName.getPackageName()).thenReturn("com.android.nfc");
when(mockAidCache.getPreferredService()).thenReturn(componentName);
- mHostEmulation.onPollingLoopDetected(pollingFrame);
- Bundle resultBundle = mHostEmulation.mPendingPollingLoopFrames.get(0);
- Assert.assertEquals(pollingFrame, resultBundle);
+ mHostEmulation.onPollingLoopDetected(pollingFrames);
+ PollingFrame resultPollingFrame = mHostEmulation.mPendingPollingLoopFrames.get(0);
+ Assert.assertEquals(pollingFrame, resultPollingFrame);
}
@Test
public void testOnPollingLoopDetectedServiceBound() {
if (!mNfcSupported) return;
- Bundle pollingLoopTypeOnFrame = mock(Bundle.class);
- Bundle pollingLoopTypeOffFrame = mock(Bundle.class);
- when(pollingLoopTypeOnFrame.getChar(HostApduService.POLLING_LOOP_TYPE_KEY))
- .thenReturn(HostApduService.POLLING_LOOP_TYPE_ON);
- when(pollingLoopTypeOffFrame.getChar(HostApduService.POLLING_LOOP_TYPE_KEY))
- .thenReturn(HostApduService.POLLING_LOOP_TYPE_OFF);
+ PollingFrame pollingLoopTypeOnFrame = mock(PollingFrame.class);
+ ArrayList<PollingFrame> pollingLoopTypeOnFrames = new ArrayList<PollingFrame>();
+ pollingLoopTypeOnFrames.add(pollingLoopTypeOnFrame);
+ PollingFrame pollingLoopTypeOffFrame = mock(PollingFrame.class);
+ ArrayList<PollingFrame> pollingLoopTypeOffFrames = new ArrayList<PollingFrame>();
+ pollingLoopTypeOffFrames.add(pollingLoopTypeOffFrame);
+ when(pollingLoopTypeOnFrame.getType())
+ .thenReturn(PollingFrame.POLLING_LOOP_TYPE_ON);
+ when(pollingLoopTypeOffFrame.getType())
+ .thenReturn(PollingFrame.POLLING_LOOP_TYPE_OFF);
ComponentName componentName = mock(ComponentName.class);
when(componentName.getPackageName()).thenReturn("com.android.nfc");
when(mockAidCache.getPreferredService()).thenReturn(componentName);
IBinder iBinder = new Binder();
ServiceConnection serviceConnection = mHostEmulation.getServiceConnection();
serviceConnection.onServiceConnected(componentName, iBinder);
- mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrame);
- mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrame);
- mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrame);
- mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrame);
+ mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrames);
+ mHostEmulation.onPollingLoopDetected(pollingLoopTypeOnFrames);
+ mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrames);
+ mHostEmulation.onPollingLoopDetected(pollingLoopTypeOffFrames);
IBinder mActiveService = mHostEmulation.getMessenger();
Assert.assertNotNull(mActiveService);
Assert.assertEquals(iBinder, mActiveService);
diff --git a/tests/unit/src/com/android/nfc/cardemulation/PreferredServicesTest.java b/tests/unit/src/com/android/nfc/cardemulation/PreferredServicesTest.java
new file mode 100644
index 0000000..37d5471
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/PreferredServicesTest.java
@@ -0,0 +1,608 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.net.Uri;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.provider.Settings;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.ForegroundUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@RunWith(AndroidJUnit4.class)
+public class PreferredServicesTest {
+
+ private PreferredServices services;
+ private MockitoSession mStaticMockSession;
+ private Context mContext;
+
+ @Mock
+ private RegisteredServicesCache mServicesCache;
+ @Mock
+ private PreferredServices.Callback mCallback;
+ @Mock
+ private RegisteredAidCache mAidCache;
+ @Mock
+ private WalletRoleObserver mObserver;
+ @Mock
+ private ForegroundUtils mForegroundUtils;
+ @Mock
+ private ContentResolver mContentResolver;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private ActivityManager mActivityManager;
+ @Mock
+ private ApduServiceInfo mServiceInfoPayment;
+ @Mock
+ private ApduServiceInfo mServiceInfoNonPayment;
+ @Mock
+ private UserHandle mUserHandle;
+ @Mock
+ private PrintWriter mPrintWriter;
+ @Mock
+ private RegisteredAidCache.AidResolveInfo mResolveInfo;
+
+ @Captor
+ private ArgumentCaptor<Integer> userIdCaptor;
+ @Captor
+ private ArgumentCaptor<ComponentName> candidateCaptor;
+
+ private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
+ private static final ComponentName TEST_COMPONENT
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+ private static final int USER_ID = 1;
+ private static final int FOREGROUND_UID = 7;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(ForegroundUtils.class)
+ .mockStatic(ActivityManager.class)
+ .mockStatic(UserHandle.class)
+ .mockStatic(Settings.Secure.class)
+ .mockStatic(ComponentName.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ mContext = new ContextWrapper(InstrumentationRegistry.getInstrumentation().getTargetContext()) {
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.ACTIVITY_SERVICE.equals(name)) {
+ return (ActivityManager) mActivityManager;
+ } else if (Context.USER_SERVICE.equals(name)) {
+ return (UserManager) mUserManager;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return mContext;
+ }
+
+ @Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+ };
+
+ when(ForegroundUtils.getInstance(any(ActivityManager.class))).thenReturn(mForegroundUtils);
+ when(ActivityManager.getCurrentUser()).thenReturn(USER_ID);
+ doNothing().when(mContentResolver)
+ .registerContentObserverAsUser(any(Uri.class), anyBoolean(), any(), any(UserHandle.class));
+ doNothing().when(mCallback).onPreferredPaymentServiceChanged(anyInt(), any());
+ when(Settings.Secure.getString(any(ContentResolver.class), anyString())).thenReturn("");
+ when(Settings.Secure.getInt(any(ContentResolver.class), anyString())).thenReturn(USER_ID);
+ when(UserHandle.getUserHandleForUid(anyInt())).thenReturn(mUserHandle);
+ when(UserHandle.of(anyInt())).thenReturn(mUserHandle);
+ when(mUserHandle.getIdentifier()).thenReturn(FOREGROUND_UID);
+ when(mObserver.getDefaultWalletRoleHolder(anyInt())).thenReturn(null);
+ when(mServiceInfoPayment.getComponent()).thenReturn(TEST_COMPONENT);
+ when(mServiceInfoPayment.getAids()).thenReturn(getAids());
+ when(mServiceInfoPayment
+ .getCategoryForAid(anyString())).thenReturn(CardEmulation.CATEGORY_PAYMENT);
+ when(mServiceInfoPayment.hasCategory(eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(true);
+ when(mServiceInfoNonPayment.hasCategory(eq(CardEmulation.CATEGORY_PAYMENT))).thenReturn(false);
+ when(mServiceInfoNonPayment.getAids()).thenReturn(getAids());
+ when(mAidCache.resolveAid(anyString())).thenReturn(mResolveInfo);
+ when(mUserManager.getEnabledProfiles()).thenReturn(getUserHandles());
+ // Wallet role feature is enabled by default; several test cases set this value to false
+ when(mObserver.isWalletRoleFeatureEnabled()).thenReturn(true);
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructorWhenWalletRoleFeatureIsNotEnabled() {
+ when(mObserver.isWalletRoleFeatureEnabled()).thenReturn(false);
+
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ assertThat(services.mContext).isEqualTo(mContext);
+ assertThat(services.mWalletRoleObserver).isEqualTo(mObserver);
+ assertThat(services.mForegroundUtils).isEqualTo(mForegroundUtils);
+ assertThat(services.mServiceCache).isEqualTo(mServicesCache);
+ assertThat(services.mAidCache).isEqualTo(mAidCache);
+ assertThat(services.mCallback).isEqualTo(mCallback);
+ assertThat(services.mSettingsObserver).isNotNull();
+ verify(mContentResolver, times(2))
+ .registerContentObserverAsUser(any(), anyBoolean(), any(), any(UserHandle.class));
+ verify(mUserManager).getEnabledProfiles();
+ verify(mObserver, never()).getDefaultWalletRoleHolder(anyInt());
+ }
+
+ @Test
+ public void testConstructorWhenWalletRoleFeatureIsEnabled() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ assertThat(services.mContext).isEqualTo(mContext);
+ assertThat(services.mWalletRoleObserver).isEqualTo(mObserver);
+ assertThat(services.mForegroundUtils).isEqualTo(mForegroundUtils);
+ assertThat(services.mServiceCache).isEqualTo(mServicesCache);
+ assertThat(services.mAidCache).isEqualTo(mAidCache);
+ assertThat(services.mCallback).isEqualTo(mCallback);
+ assertThat(services.mSettingsObserver).isNotNull();
+ verify(mContentResolver, times(2))
+ .registerContentObserverAsUser(any(), anyBoolean(), any(), any(UserHandle.class));
+ verify(mUserManager).getEnabledProfiles();
+ verify(mObserver).getDefaultWalletRoleHolder(anyInt());
+ assertThat(services.mDefaultWalletHolderPaymentService).isNull();
+ verify(mCallback).onPreferredPaymentServiceChanged(anyInt(), any());
+ }
+
+ @Test
+ public void testOnWalletRoleHolderChangedWithNullPackageName() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ services.onWalletRoleHolderChanged(null, USER_ID);
+
+ verify(mCallback, times(2))
+ .onPreferredPaymentServiceChanged(userIdCaptor.capture(), candidateCaptor.capture());
+ List<Integer> userIds = userIdCaptor.getAllValues();
+ assertThat(userIds.get(0)).isEqualTo(USER_ID);
+ assertThat(userIds.get(1)).isEqualTo(USER_ID);
+ List<ComponentName> candidates = candidateCaptor.getAllValues();
+ assertThat(candidates.get(0)).isNull();
+ assertThat(candidates.get(1)).isNull();
+ assertThat(services.mDefaultWalletHolderPaymentService).isNull();
+ }
+
+ @Test
+ public void testOnWalletRoleHolderChangedWithExistingPackageNameAndExistingServiceInfos() {
+ when(mServicesCache.getInstalledServices(eq(USER_ID))).thenReturn(getPaymentServices());
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ services.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+
+ assertThat(services.mUserIdDefaultWalletHolder).isEqualTo(USER_ID);
+ verify(mCallback, times(2))
+ .onPreferredPaymentServiceChanged(userIdCaptor.capture(), candidateCaptor.capture());
+ List<Integer> userIds = userIdCaptor.getAllValues();
+ assertThat(userIds.get(0)).isEqualTo(USER_ID);
+ assertThat(userIds.get(1)).isEqualTo(USER_ID);
+ List<ComponentName> candidates = candidateCaptor.getAllValues();
+ assertThat(candidates.get(0)).isNull();
+ assertThat(candidates.get(1)).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mDefaultWalletHolderPaymentService).isEqualTo(TEST_COMPONENT);
+ }
+
+ @Test
+ public void testOnWalletRoleHolderChangedWithExistingPackageNameAndNoServiceInfo() {
+ ArrayList<ApduServiceInfo> emptyList = new ArrayList<>();
+ when(mServicesCache.getInstalledServices(eq(USER_ID))).thenReturn(emptyList);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ services.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+
+ assertThat(services.mUserIdDefaultWalletHolder).isEqualTo(USER_ID);
+ verify(mCallback).onPreferredPaymentServiceChanged(anyInt(), any());
+ assertThat(services.mDefaultWalletHolderPaymentService).isNull();
+ }
+
+ @Test
+ public void testOnWalletRoleHolderChangedWithIncorrectPackageName() {
+ when(mServicesCache.getInstalledServices(eq(USER_ID))).thenReturn(getPaymentServices());
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ services.onWalletRoleHolderChanged(/* defaultWalletHolderPackageName = */ "", USER_ID);
+
+ assertThat(services.mUserIdDefaultWalletHolder).isEqualTo(USER_ID);
+ verify(mCallback).onPreferredPaymentServiceChanged(anyInt(), any());
+ assertThat(services.mDefaultWalletHolderPaymentService).isNull();
+ }
+
+ @Test
+ public void testSetDefaultForNextTapWithNonNullService_NotifyChange() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+
+ boolean result = services.setDefaultForNextTap(USER_ID, TEST_COMPONENT);
+
+ assertThat(result).isTrue();
+ assertThat(services.mNextTapDefault).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mNextTapDefaultUserId).isEqualTo(USER_ID);
+ assertThat(services.mForegroundCurrent).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(FOREGROUND_UID);
+ verify(mCallback)
+ .onPreferredForegroundServiceChanged(userIdCaptor.capture(), candidateCaptor.capture());
+ assertThat(userIdCaptor.getValue()).isEqualTo(USER_ID);
+ assertThat(candidateCaptor.getValue()).isEqualTo(TEST_COMPONENT);
+ }
+
+ @Test
+ public void testSetDefaultForNextTapWithNullService_NoChange() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+ services.mForegroundRequested = null;
+ services.mForegroundCurrent = null;
+
+ boolean result = services.setDefaultForNextTap(USER_ID, /* service = */ null);
+
+ assertThat(result).isTrue();
+ assertThat(services.mNextTapDefault).isNull();
+ assertThat(services.mNextTapDefaultUserId).isEqualTo(USER_ID);
+ assertThat(services.mForegroundCurrent).isEqualTo(null);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(0);
+ verify(mCallback, never()).onPreferredForegroundServiceChanged(anyInt(), any());
+ }
+
+ @Test
+ public void testSetDefaultForNextTapWithNonNullService_NoChange() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundCurrent = TEST_COMPONENT;
+
+ boolean result = services.setDefaultForNextTap(FOREGROUND_UID, TEST_COMPONENT);
+
+ assertThat(result).isTrue();
+ assertThat(services.mNextTapDefault).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mNextTapDefaultUserId).isEqualTo(FOREGROUND_UID);
+ assertThat(services.mForegroundCurrent).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(0);
+ verify(mCallback, never()).onPreferredForegroundServiceChanged(anyInt(), any());
+ }
+
+ @Test
+ public void testSetDefaultForNextTapWithNullService_NotifyChange() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+ services.mForegroundRequested = null;
+ services.mForegroundCurrent = TEST_COMPONENT;
+
+ boolean result = services.setDefaultForNextTap(USER_ID, /* service = */ null);
+
+ assertThat(result).isTrue();
+ assertThat(services.mNextTapDefault).isNull();
+ assertThat(services.mNextTapDefaultUserId).isEqualTo(USER_ID);
+ assertThat(services.mForegroundCurrent).isEqualTo(null);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(FOREGROUND_UID);
+ verify(mCallback)
+ .onPreferredForegroundServiceChanged(userIdCaptor.capture(), candidateCaptor.capture());
+ assertThat(userIdCaptor.getValue()).isEqualTo(FOREGROUND_UID);
+ assertThat(candidateCaptor.getValue()).isNull();
+ }
+
+ @Test
+ public void testOnServicesUpdatedWithNullForeground_NoChange() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundCurrent = null;
+
+ services.onServicesUpdated();
+
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(0);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(0);
+ }
+
+ @Test
+ public void testOnServicesUpdatedWithNonNullForegroundAndPaymentServiceInfo_CommitsChange() {
+ when(mServicesCache.getService(anyInt(), any())).thenReturn(mServiceInfoPayment);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundCurrent = TEST_COMPONENT;
+ services.mForegroundCurrentUid = FOREGROUND_UID;
+ services.mPaymentDefaults.currentPreferred = null;
+ services.mPaymentDefaults.preferForeground = false;
+
+ services.onServicesUpdated();
+
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(-1);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(-1);
+ }
+
+ @Test
+ public void testOnServicesUpdatedWithNonNullForegroundAndNonPaymentServiceInfo_CommitsChange() {
+ when(mServicesCache.getService(anyInt(), any())).thenReturn(mServiceInfoNonPayment);
+ mResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ mResolveInfo.defaultService = mServiceInfoNonPayment;
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundCurrent = TEST_COMPONENT;
+ services.mForegroundCurrentUid = FOREGROUND_UID;
+ services.mPaymentDefaults.currentPreferred = null;
+ services.mPaymentDefaults.mUserHandle = mUserHandle;
+ services.mPaymentDefaults.preferForeground = false;
+
+ services.onServicesUpdated();
+
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(-1);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(-1);
+ }
+
+ @Test
+ public void testOnServicesUpdatedWithNonNullForegroundAndNonPaymentServiceInfo_NoChange() {
+ when(mServicesCache.getService(anyInt(), any())).thenReturn(mServiceInfoNonPayment);
+ mResolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
+ mResolveInfo.defaultService = null;
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundCurrent = TEST_COMPONENT;
+ services.mForegroundCurrentUid = FOREGROUND_UID;
+ services.mPaymentDefaults.currentPreferred = null;
+ services.mPaymentDefaults.mUserHandle = mUserHandle;
+ services.mPaymentDefaults.preferForeground = false;
+
+ services.onServicesUpdated();
+
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(0);
+ assertThat(services.mForegroundCurrentUid).isEqualTo(FOREGROUND_UID);
+ }
+
+ @Test
+ public void testRegisterPreferredForegroundServiceWithSuccess() {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt())).thenReturn(true);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mPaymentDefaults.currentPreferred = TEST_COMPONENT;
+
+ boolean result = services.registerPreferredForegroundService(TEST_COMPONENT, USER_ID);
+
+ assertThat(result).isTrue();
+ assertThat(services.mForegroundRequested).isEqualTo(TEST_COMPONENT);
+ assertThat(services.mForegroundUid).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testRegisterPreferredForegroundServiceWithFailure() {
+ when(mForegroundUtils.registerUidToBackgroundCallback(any(), anyInt())).thenReturn(false);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mPaymentDefaults.currentPreferred = TEST_COMPONENT;
+
+ boolean result = services.registerPreferredForegroundService(TEST_COMPONENT, USER_ID);
+
+ assertThat(result).isFalse();
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(0);
+ }
+
+ @Test
+ public void testUnregisteredPreferredForegroundServiceInForeground_ReturnsSuccess() {
+ when(mForegroundUtils.isInForeground(anyInt())).thenReturn(true);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+
+ boolean result = services.unregisteredPreferredForegroundService(FOREGROUND_UID);
+
+ assertThat(result).isTrue();
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(-1);
+ }
+
+ @Test
+ public void testUnregisteredPreferredForegroundServiceInForeground_ReturnsFailure() {
+ when(mForegroundUtils.isInForeground(anyInt())).thenReturn(true);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+
+ boolean result = services.unregisteredPreferredForegroundService(USER_ID);
+
+ assertThat(result).isFalse();
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(FOREGROUND_UID);
+ }
+
+ @Test
+ public void testUnregisteredPreferredForegroundServiceNotInForeground_ReturnsFailure() {
+ when(mForegroundUtils.isInForeground(anyInt())).thenReturn(false);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ boolean result = services.unregisteredPreferredForegroundService(USER_ID);
+
+ assertThat(result).isFalse();
+ assertThat(services.mForegroundRequested).isNull();
+ assertThat(services.mForegroundUid).isEqualTo(0);
+ }
+
+ @Test
+ public void testOnUidToBackground_SuccessfullyUnregistersService() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+
+ services.onUidToBackground(FOREGROUND_UID);
+
+ assertThat(services.mForegroundUid).isEqualTo(-1);
+ }
+
+ @Test
+ public void testOnUidToBackground_FailsToUnregisterService() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mForegroundUid = FOREGROUND_UID;
+
+ services.onUidToBackground(USER_ID);
+
+ assertThat(services.mForegroundUid).isEqualTo(FOREGROUND_UID);
+ }
+
+ @Test
+ public void testOnHostEmulationActivated() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mNextTapDefault = TEST_COMPONENT;
+
+ services.onHostEmulationActivated();
+
+ assertThat(services.mClearNextTapDefault).isTrue();
+ }
+
+ @Test
+ public void testOnHostEmulationDeactivated() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mClearNextTapDefault = true;
+ services.mNextTapDefault = TEST_COMPONENT;
+
+ services.onHostEmulationDeactivated();
+
+ assertThat(services.mNextTapDefault).isNull();
+ assertThat(services.mClearNextTapDefault).isFalse();
+ }
+
+ @Test
+ public void testOnUserSwitchedWithChange() {
+ when(mObserver.isWalletRoleFeatureEnabled()).thenReturn(false);
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mPaymentDefaults.preferForeground = false;
+ services.mPaymentDefaults.currentPreferred = TEST_COMPONENT;
+
+ services.onUserSwitched(USER_ID);
+
+ assertThat(services.mPaymentDefaults.preferForeground).isTrue();
+ assertThat(services.mPaymentDefaults.settingsDefault).isEqualTo(null);
+ assertThat(services.mPaymentDefaults.currentPreferred).isEqualTo(null);
+ assertThat(services.mPaymentDefaults.mUserHandle).isEqualTo(mUserHandle);
+ verify(mCallback)
+ .onPreferredPaymentServiceChanged(userIdCaptor.capture(), candidateCaptor.capture());
+ assertThat(userIdCaptor.getValue()).isEqualTo(FOREGROUND_UID);
+ assertThat(candidateCaptor.getValue()).isEqualTo(null);
+ }
+
+ @Test
+ public void testOnUserSwitchedWithNoChange() throws Exception {
+ when(mUserManager.getEnabledProfiles()).thenReturn(getUserHandles());
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mPaymentDefaults.preferForeground = false;
+ services.mPaymentDefaults.currentPreferred = null;
+ verify(mCallback).onPreferredPaymentServiceChanged(anyInt(), any());
+
+ services.onUserSwitched(USER_ID);
+
+ assertThat(services.mPaymentDefaults.preferForeground).isTrue();
+ assertThat(services.mPaymentDefaults.settingsDefault).isEqualTo(null);
+ assertThat(services.mPaymentDefaults.currentPreferred).isEqualTo(null);
+ assertThat(services.mPaymentDefaults.mUserHandle).isEqualTo(null);
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ @Test
+ public void testPackageHasPreferredServiceWithNullPackageName_ReturnsFalse() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ boolean result = services.packageHasPreferredService(/* packageName = */ null);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void testPackageHasPreferredServiceWithMatchingPackageName_ReturnsTrue() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+ services.mPaymentDefaults.currentPreferred = TEST_COMPONENT;
+
+ boolean result = services.packageHasPreferredService(WALLET_HOLDER_PACKAGE_NAME);
+
+ assertThat(result).isTrue();
+ }
+
+ @Test
+ public void testPackageHasPreferredServiceWithNonMatchingPackageName_ReturnsFalse() {
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ boolean result = services.packageHasPreferredService(WALLET_HOLDER_PACKAGE_NAME);
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void testDump() {
+ when(mUserManager.getUserName()).thenReturn("");
+ services = new PreferredServices(mContext, mServicesCache, mAidCache, mObserver, mCallback);
+
+ services.dump(null, mPrintWriter, null);
+
+ verify(mPrintWriter, times(8)).println(anyString());
+ }
+
+ private ArrayList<String> getAids() {
+ ArrayList<String> aids = new ArrayList<>();
+ aids.add("aid");
+ return aids;
+ }
+
+ private ArrayList<ApduServiceInfo> getPaymentServices() {
+ ArrayList<ApduServiceInfo> serviceInfos = new ArrayList<>();
+ serviceInfos.add(mServiceInfoPayment);
+ return serviceInfos;
+ }
+
+ private ArrayList<UserHandle> getUserHandles() {
+ ArrayList<UserHandle> list = new ArrayList<>();
+ list.add(mUserHandle);
+ return list;
+ }
+}
\ No newline at end of file
diff --git a/tests/unit/src/com/android/nfc/cardemulation/RegisteredAidCacheTest.java b/tests/unit/src/com/android/nfc/cardemulation/RegisteredAidCacheTest.java
new file mode 100644
index 0000000..69f760e
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/RegisteredAidCacheTest.java
@@ -0,0 +1,626 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.NfcService;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class RegisteredAidCacheTest {
+
+ private static final String PREFIX_AID = "ASDASD*";
+ private static final String SUBSET_AID = "ASDASD#";
+ private static final String EXACT_AID = "TASDASD";
+ private static final String PAYMENT_AID_1 = "A000000004101012";
+ private static final String PAYMENT_AID_2 = "A000000004101018";
+ private static final String NON_PAYMENT_AID_1 = "F053414950454D";
+ private static final String PREFIX_PAYMENT_AID = "A000000004*";
+ private static final String NFC_FOREGROUND_PACKAGE_NAME = "com.android.test.foregroundnfc";
+ private static final String NON_PAYMENT_NFC_PACKAGE_NAME = "com.android.test.nonpaymentnfc";
+ private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
+ private static final String WALLET_HOLDER_2_PACKAGE_NAME = "com.android.test.walletroleholder2";
+
+ private static final ComponentName WALLET_PAYMENT_SERVICE
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.WalletRoleHolderApduService");
+
+ private static final ComponentName WALLET_PAYMENT_SERVICE_2
+ = new ComponentName(WALLET_HOLDER_PACKAGE_NAME,
+ "com.android.test.walletroleholder.XWalletRoleHolderApduService");
+ private static final ComponentName FOREGROUND_SERVICE
+ = new ComponentName(NFC_FOREGROUND_PACKAGE_NAME,
+ "com.android.test.foregroundnfc.ForegroundApduService");
+ private static final ComponentName NON_PAYMENT_SERVICE =
+ new ComponentName(NON_PAYMENT_NFC_PACKAGE_NAME,
+ "com.android.test.nonpaymentnfc.NonPaymentApduService");
+
+ private static final ComponentName PAYMENT_SERVICE =
+ new ComponentName(WALLET_HOLDER_2_PACKAGE_NAME,
+ "com.android.test.walletroleholder.WalletRoleHolderXApduService");
+
+ private static final int USER_ID = 0;
+ private static final UserHandle USER_HANDLE = UserHandle.of(USER_ID);
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private WalletRoleObserver mWalletRoleObserver;
+ @Mock
+ private AidRoutingManager mAidRoutingManager;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private NfcService mNfcService;
+ @Captor
+ private ArgumentCaptor<HashMap<String, AidRoutingManager.AidEntry>> mRoutingEntryMapCaptor;
+
+ private MockitoSession mStaticMockSession;
+
+ RegisteredAidCache mRegisteredAidCache;
+
+ @Before
+ public void setUp() {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(ActivityManager.class)
+ .mockStatic(NfcService.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ when(ActivityManager.getCurrentUser()).thenReturn(USER_ID);
+ when(NfcService.getInstance()).thenReturn(mNfcService);
+ when(mNfcService.getNciVersion()).thenReturn(NfcService.NCI_VERSION_1_0);
+ when(mUserManager.getProfileParent(eq(USER_HANDLE))).thenReturn(USER_HANDLE);
+ when(mContext.createContextAsUser(
+ any(), anyInt())).thenReturn(mContext);
+ when(mContext.getSystemService(eq(UserManager.class))).thenReturn(mUserManager);
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testConstructor_supportsPrefixAndSubset() {
+ supportPrefixAndSubset(true);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ Assert.assertTrue(mRegisteredAidCache.supportsAidPrefixRegistration());
+ Assert.assertTrue(mRegisteredAidCache.supportsAidSubsetRegistration());
+ }
+
+ @Test
+ public void testConstructor_doesNotSupportsPrefixAndSubset() {
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ Assert.assertFalse(mRegisteredAidCache.supportsAidPrefixRegistration());
+ Assert.assertFalse(mRegisteredAidCache.supportsAidSubsetRegistration());
+ }
+
+ @Test
+ public void testAidStaticMethods() {
+ Assert.assertTrue(RegisteredAidCache.isPrefix(PREFIX_AID));
+ Assert.assertTrue(RegisteredAidCache.isSubset(SUBSET_AID));
+ Assert.assertTrue(RegisteredAidCache.isExact(EXACT_AID));
+
+ Assert.assertFalse(RegisteredAidCache.isPrefix(EXACT_AID));
+ Assert.assertFalse(RegisteredAidCache.isSubset(EXACT_AID));
+ Assert.assertFalse(RegisteredAidCache.isExact(PREFIX_AID));
+ Assert.assertFalse(RegisteredAidCache.isExact(SUBSET_AID));
+ }
+
+ @Test
+ public void testAidConflictResolution_walletRoleEnabledNfcDisabled_foregroundWins() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = false;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ FOREGROUND_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.generateUserApduServiceInfoLocked(USER_ID, apduServiceInfos);
+ mRegisteredAidCache.generateServiceMapLocked(apduServiceInfos);
+ mRegisteredAidCache.onPreferredForegroundServiceChanged(USER_ID, FOREGROUND_SERVICE);
+ RegisteredAidCache.AidResolveInfo resolveInfo
+ = mRegisteredAidCache.resolveAid(PAYMENT_AID_1);
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ Assert.assertEquals(resolveInfo.defaultService.getComponent(), FOREGROUND_SERVICE);
+ Assert.assertEquals(mRegisteredAidCache.getPreferredService(), FOREGROUND_SERVICE);
+ Assert.assertEquals(resolveInfo.services.size(), 1);
+ Assert.assertEquals(resolveInfo.category, CardEmulation.CATEGORY_PAYMENT);
+ verifyNoMoreInteractions(mAidRoutingManager);
+ }
+
+ @Test
+ public void testAidConflictResolution_walletRoleEnabledNfcEnabled_walletWins() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = true;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.generateUserApduServiceInfoLocked(USER_ID, apduServiceInfos);
+ mRegisteredAidCache.generateServiceMapLocked(apduServiceInfos);
+ mRegisteredAidCache.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+ RegisteredAidCache.AidResolveInfo paymentResolveInfo
+ = mRegisteredAidCache.resolveAid(PAYMENT_AID_1);
+ RegisteredAidCache.AidResolveInfo nonPaymentResolveInfo
+ = mRegisteredAidCache.resolveAid(NON_PAYMENT_AID_1);
+
+ Assert.assertEquals(paymentResolveInfo.defaultService.getComponent(),
+ WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(paymentResolveInfo.services.size(), 1);
+ Assert.assertEquals(paymentResolveInfo.category, CardEmulation.CATEGORY_PAYMENT);
+ Assert.assertEquals(nonPaymentResolveInfo.defaultService.getComponent(),
+ NON_PAYMENT_SERVICE);
+ Assert.assertEquals(nonPaymentResolveInfo.services.size(), 1);
+ Assert.assertEquals(nonPaymentResolveInfo.category, CardEmulation.CATEGORY_OTHER);
+ verify(mAidRoutingManager).configureRouting(mRoutingEntryMapCaptor.capture(),
+ eq(false));
+ HashMap<String, AidRoutingManager.AidEntry> routingEntries =
+ mRoutingEntryMapCaptor.getValue();
+ Assert.assertTrue(routingEntries.containsKey(PAYMENT_AID_1));
+ Assert.assertTrue(routingEntries.containsKey(NON_PAYMENT_AID_1));
+ Assert.assertTrue(routingEntries.get(PAYMENT_AID_1).isOnHost);
+ Assert.assertTrue(routingEntries.get(NON_PAYMENT_AID_1).isOnHost);
+ Assert.assertNull(routingEntries.get(PAYMENT_AID_1).offHostSE);
+ Assert.assertNull(routingEntries.get(NON_PAYMENT_AID_1).offHostSE);
+ Assert.assertTrue(mRegisteredAidCache.isRequiresScreenOnServiceExist());
+ }
+
+ @Test
+ public void testAidConflictResolution_walletRoleEnabledNfcEnabledPreFixAid_walletWins() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(true);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = true;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PREFIX_PAYMENT_AID),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.generateUserApduServiceInfoLocked(USER_ID, apduServiceInfos);
+ mRegisteredAidCache.generateServiceMapLocked(apduServiceInfos);
+ mRegisteredAidCache.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+ RegisteredAidCache.AidResolveInfo paymentResolveInfo
+ = mRegisteredAidCache.resolveAid(PAYMENT_AID_1);
+ RegisteredAidCache.AidResolveInfo nonPaymentResolveInfo
+ = mRegisteredAidCache.resolveAid(NON_PAYMENT_AID_1);
+
+ Assert.assertEquals(paymentResolveInfo.defaultService.getComponent(),
+ WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(paymentResolveInfo.services.size(), 1);
+ Assert.assertEquals(paymentResolveInfo.category, CardEmulation.CATEGORY_PAYMENT);
+ Assert.assertEquals(nonPaymentResolveInfo.defaultService.getComponent(),
+ NON_PAYMENT_SERVICE);
+ Assert.assertEquals(nonPaymentResolveInfo.services.size(), 1);
+ Assert.assertEquals(nonPaymentResolveInfo.category, CardEmulation.CATEGORY_OTHER);
+ }
+
+ @Test
+ public void testAidConflictResolution_walletRoleEnabled_twoServicesOnWallet_firstServiceWins() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE_2,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.generateUserApduServiceInfoLocked(USER_ID, apduServiceInfos);
+ mRegisteredAidCache.generateServiceMapLocked(apduServiceInfos);
+ mRegisteredAidCache.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+ RegisteredAidCache.AidResolveInfo resolveInfo
+ = mRegisteredAidCache.resolveAid(PAYMENT_AID_1);
+ Assert.assertEquals(resolveInfo.defaultService.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(resolveInfo.services.size(), 2);
+ Assert.assertEquals(resolveInfo.category, CardEmulation.CATEGORY_PAYMENT);
+ }
+
+ @Test
+ public void testOnServicesUpdated_walletRoleEnabled() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = true;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ true,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ true,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.onServicesUpdated(USER_ID, apduServiceInfos);
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ Assert.assertTrue(mRegisteredAidCache.mAidServices.containsKey(PAYMENT_AID_1));
+ Assert.assertTrue(mRegisteredAidCache.mAidServices.containsKey(NON_PAYMENT_AID_1));
+ Assert.assertEquals(mRegisteredAidCache.mAidServices.get(PAYMENT_AID_1).size(), 2);
+ Assert.assertEquals(mRegisteredAidCache.mAidServices.get(NON_PAYMENT_AID_1).size(), 1);
+ Assert.assertEquals(mRegisteredAidCache.mAidServices.get(PAYMENT_AID_1).get(0)
+ .service.getComponent(), WALLET_PAYMENT_SERVICE);
+ Assert.assertEquals(mRegisteredAidCache.mAidServices.get(PAYMENT_AID_1).get(1)
+ .service.getComponent(), PAYMENT_SERVICE);
+ verify(mAidRoutingManager).configureRouting(mRoutingEntryMapCaptor.capture(),
+ eq(false));
+ HashMap<String, AidRoutingManager.AidEntry> routingEntries =
+ mRoutingEntryMapCaptor.getValue();
+ Assert.assertTrue(routingEntries.containsKey(NON_PAYMENT_AID_1));
+ Assert.assertTrue(routingEntries.get(NON_PAYMENT_AID_1).isOnHost);
+ Assert.assertNull(routingEntries.get(NON_PAYMENT_AID_1).offHostSE);
+ Assert.assertTrue(mRegisteredAidCache.isRequiresScreenOnServiceExist());
+ }
+
+ @Test
+ public void testOnNfcEnabled() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.generateUserApduServiceInfoLocked(USER_ID, apduServiceInfos);
+ mRegisteredAidCache.generateServiceMapLocked(apduServiceInfos);
+ mRegisteredAidCache.onNfcEnabled();
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ verify(mAidRoutingManager).configureRouting(mRoutingEntryMapCaptor.capture(),
+ eq(false));
+ Assert.assertFalse(mRegisteredAidCache.isRequiresScreenOnServiceExist());
+ }
+
+ @Test
+ public void testOnNfcDisabled() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.onNfcDisabled();
+
+ verify(mAidRoutingManager).supportsAidPrefixRouting();
+ verify(mAidRoutingManager).supportsAidSubsetRouting();
+ verify(mAidRoutingManager).onNfccRoutingTableCleared();
+ }
+
+ @Test
+ public void testPollingLoopFilterToForeground_walletRoleEnabled_walletSet() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = true;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ FOREGROUND_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.onWalletRoleHolderChanged(WALLET_HOLDER_PACKAGE_NAME, USER_ID);
+ mRegisteredAidCache.onPreferredForegroundServiceChanged(USER_ID, FOREGROUND_SERVICE);
+
+ ApduServiceInfo resolvedApdu =
+ mRegisteredAidCache.resolvePollingLoopFilterConflict(apduServiceInfos);
+
+ Assert.assertEquals(resolvedApdu, apduServiceInfos.get(1));
+ }
+
+ @Test
+ public void testPollingLoopFilterToWallet_walletRoleEnabled_walletSet() {
+ setWalletRoleFlag(true);
+ supportPrefixAndSubset(false);
+ mRegisteredAidCache = new RegisteredAidCache(mContext, mWalletRoleObserver,
+ mAidRoutingManager);
+ mRegisteredAidCache.mNfcEnabled = true;
+
+ List<ApduServiceInfo> apduServiceInfos = new ArrayList<>();
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ WALLET_PAYMENT_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ FOREGROUND_SERVICE,
+ true,
+ List.of(PAYMENT_AID_1, NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_PAYMENT, CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+ apduServiceInfos.add(createServiceInfoForAidRouting(
+ NON_PAYMENT_SERVICE,
+ true,
+ List.of(NON_PAYMENT_AID_1),
+ List.of(CardEmulation.CATEGORY_OTHER),
+ false,
+ false,
+ USER_ID,
+ true));
+
+ mRegisteredAidCache.mDefaultWalletHolderPackageName = WALLET_HOLDER_PACKAGE_NAME;
+
+ ApduServiceInfo resolvedApdu =
+ mRegisteredAidCache.resolvePollingLoopFilterConflict(apduServiceInfos);
+
+ Assert.assertEquals(resolvedApdu, apduServiceInfos.get(0));
+ }
+
+ private void setWalletRoleFlag(boolean flag) {
+ when(mWalletRoleObserver.isWalletRoleFeatureEnabled()).thenReturn(flag);
+ }
+
+ private void supportPrefixAndSubset(boolean support) {
+ when(mAidRoutingManager.supportsAidPrefixRouting()).thenReturn(support);
+ when(mAidRoutingManager.supportsAidSubsetRouting()).thenReturn(support);
+ }
+
+ private static ApduServiceInfo createServiceInfoForAidRouting(ComponentName componentName,
+ boolean onHost,
+ List<String> aids,List<String> categories, boolean requiresUnlock, boolean requiresScreenOn,
+ int uid, boolean isCategoryOtherServiceEnabled) {
+ ApduServiceInfo apduServiceInfo = Mockito.mock(ApduServiceInfo.class);
+ when(apduServiceInfo.isOnHost()).thenReturn(onHost);
+ when(apduServiceInfo.getAids()).thenReturn(aids);
+ when(apduServiceInfo.getUid()).thenReturn(uid);
+ when(apduServiceInfo.requiresUnlock()).thenReturn(requiresUnlock);
+ when(apduServiceInfo.requiresScreenOn()).thenReturn(requiresScreenOn);
+ when(apduServiceInfo.isCategoryOtherServiceEnabled())
+ .thenReturn(isCategoryOtherServiceEnabled);
+ when(apduServiceInfo.getComponent()).thenReturn(componentName);
+ for (int i = 0; i < aids.size(); i++) {
+ String aid = aids.get(i);
+ String category = categories.get(i);
+ when(apduServiceInfo.getCategoryForAid(eq(aid))).thenReturn(category);
+ }
+ return apduServiceInfo;
+ }
+
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/RegisteredServicesCacheTest.java b/tests/unit/src/com/android/nfc/cardemulation/RegisteredServicesCacheTest.java
new file mode 100644
index 0000000..94df5d3
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/RegisteredServicesCacheTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulation;
+import android.nfc.cardemulation.HostApduService;
+import android.nfc.cardemulation.OffHostApduService;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.nfc.NfcService;
+import com.android.nfc.NfcStatsLog;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class RegisteredServicesCacheTest {
+
+ private static final int USER_ID = 1;
+ private static final UserHandle USER_HANDLE = UserHandle.of(USER_ID);
+ private static final UserHandle USER_HANDLE_QUITE_MODE = UserHandle.of(2);
+ private static final File DIR = new File("/");
+ private static final String ANOTHER_PACKAGE_NAME = "com.android.test.another";
+ private static final String NON_PAYMENT_NFC_PACKAGE_NAME = "com.android.test.nonpaymentnfc";
+ private static final String WALLET_HOLDER_PACKAGE_NAME = "com.android.test.walletroleholder";
+ private static final String ON_HOST_SERVICE_NAME
+ = "com.android.test.walletroleholder.OnHostApduService";
+ private static final String OFF_HOST_SERVICE_NAME
+ = "com.android.test.another.OffHostApduService";
+ private static final String NON_PAYMENT_SERVICE_NAME
+ = "com.android.test.nonpaymentnfc.NonPaymentApduService";
+ private static final ComponentName WALLET_HOLDER_SERVICE_COMPONENT =
+ new ComponentName(WALLET_HOLDER_PACKAGE_NAME, ON_HOST_SERVICE_NAME);
+ private static final ComponentName NON_PAYMENT_SERVICE_COMPONENT =
+ new ComponentName(NON_PAYMENT_NFC_PACKAGE_NAME, NON_PAYMENT_SERVICE_NAME);
+ private static final ComponentName ANOTHER_SERVICE_COMPONENT =
+ new ComponentName(ANOTHER_PACKAGE_NAME, OFF_HOST_SERVICE_NAME);
+ private static final String OFFHOST_SE_STRING = "offhostse";
+ private static final String TRUE_STRING = "true";
+ private static final String FALSE_STRING = "false";
+ private static final String ANDROID_STRING = "android";
+ private static final List<String> PAYMENT_AIDS = List.of("A000000004101011",
+ "A000000004101012", "A000000004101013");
+ private static final List<String> NON_PAYMENT_AID = List.of("F053414950454D");
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private RegisteredServicesCache.Callback mCallback;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private RegisteredServicesCache.SettingsFile mDynamicSettingsFile;
+ @Mock
+ private RegisteredServicesCache.SettingsFile mOtherSettingsFile;
+ @Mock
+ private RegisteredServicesCache.ServiceParser mServiceParser;
+ @Captor
+ private ArgumentCaptor<BroadcastReceiver> mReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<PackageManager.ResolveInfoFlags> mFlagArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<Intent> mIntentArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<List<ApduServiceInfo>> mApduServiceListCaptor;
+
+ private MockitoSession mStaticMockSession;
+ private RegisteredServicesCache mRegisteredServicesCache;
+ private Map<String, ApduServiceInfo> mMappedServices;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException, XmlPullParserException,
+ IOException {
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(com.android.nfc.flags.Flags.class)
+ .mockStatic(ActivityManager.class)
+ .mockStatic(NfcStatsLog.class)
+ .mockStatic(UserHandle.class)
+ .mockStatic(NfcAdapter.class)
+ .mockStatic(NfcService.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+ mMappedServices = new HashMap<>();
+ when(UserHandle.getUserHandleForUid(eq(USER_ID))).thenReturn(USER_HANDLE);
+ when(UserHandle.of(eq(USER_ID))).thenReturn(USER_HANDLE);
+ when(ActivityManager.getCurrentUser()).thenReturn(USER_ID);
+ when(mContext.getSystemService(eq(UserManager.class))).thenReturn(mUserManager);
+ when(mContext.getFilesDir()).thenReturn(DIR);
+ when(mContext.createContextAsUser(
+ any(), anyInt())).thenReturn(mContext);
+ when(mContext.createPackageContextAsUser(
+ any(), anyInt(), any())).thenReturn(mContext);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ List<UserHandle> enabledProfiles = new ArrayList<>();
+ enabledProfiles.add(USER_HANDLE);
+ enabledProfiles.add(USER_HANDLE_QUITE_MODE);
+ when(mUserManager.getEnabledProfiles()).thenReturn(enabledProfiles);
+ when(mUserManager.isQuietModeEnabled(eq(USER_HANDLE))).thenReturn(false);
+ when(mUserManager.isQuietModeEnabled(eq(USER_HANDLE_QUITE_MODE))).thenReturn(true);
+ List<ResolveInfo> onHostServicesList = new ArrayList<>();
+ onHostServicesList.add(createServiceResolveInfo(true,
+ WALLET_HOLDER_PACKAGE_NAME, ON_HOST_SERVICE_NAME,
+ List.of(CardEmulation.CATEGORY_PAYMENT)));
+ onHostServicesList.add(createServiceResolveInfo(false,
+ NON_PAYMENT_NFC_PACKAGE_NAME, NON_PAYMENT_SERVICE_NAME,
+ List.of(CardEmulation.CATEGORY_OTHER)));
+ List<ResolveInfo> offHostServicesList = new ArrayList<>();
+ offHostServicesList.add(createServiceResolveInfo(true, ANOTHER_PACKAGE_NAME,
+ OFF_HOST_SERVICE_NAME, List.of(CardEmulation.CATEGORY_OTHER)));
+ when(mPackageManager.queryIntentServicesAsUser(
+ any(), any(), any())).thenAnswer(invocation -> {
+ Intent intent = invocation.getArgument(0);
+ if(intent.getAction().equals(OffHostApduService.SERVICE_INTERFACE)) {
+ return offHostServicesList;
+ }
+ if(intent.getAction().equals(HostApduService.SERVICE_INTERFACE)) {
+ return onHostServicesList;
+ }
+ return List.of();
+ });
+ when(mServiceParser.parseApduService(any(), any(), anyBoolean()))
+ .thenAnswer(invocation -> {
+ ResolveInfo resolveInfo = invocation.getArgument(1);
+ if(mMappedServices.containsKey(resolveInfo.serviceInfo.name)) {
+ return mMappedServices.get(resolveInfo.serviceInfo.name);
+ }
+ return null;
+ });
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ // Intent filter registration is actually not happening. It's just a mock verification.
+ @SuppressWarnings({"UnspecifiedRegisterReceiverFlag", "GuardedBy"})
+ @Test
+ public void testConstructor() {
+ mRegisteredServicesCache = new RegisteredServicesCache(mContext, mCallback);
+
+ // Verify that the users handles are populated correctly
+ Assert.assertEquals(1, mRegisteredServicesCache.mUserHandles.size());
+ Assert.assertEquals(mRegisteredServicesCache.mUserHandles.get(0), USER_HANDLE);
+ // Verify that broadcast receivers for apk changes are created and registered properly
+ Assert.assertNotNull(mRegisteredServicesCache.mReceiver.get());
+ verify(mContext).createContextAsUser(eq(USER_HANDLE), eq(0));
+ verify(mContext, times(2)).registerReceiverForAllUsers(
+ mReceiverArgumentCaptor.capture(), mIntentFilterArgumentCaptor.capture(),
+ eq(null), eq(null));
+ IntentFilter packageInstallTrackerIntent = mIntentFilterArgumentCaptor
+ .getAllValues().get(0);
+ Assert.assertTrue(packageInstallTrackerIntent.hasAction(Intent.ACTION_PACKAGE_ADDED));
+ Assert.assertTrue(packageInstallTrackerIntent.hasAction(Intent.ACTION_PACKAGE_CHANGED));
+ Assert.assertTrue(packageInstallTrackerIntent.hasAction(Intent.ACTION_PACKAGE_REMOVED));
+ Assert.assertTrue(packageInstallTrackerIntent.hasAction(Intent.ACTION_PACKAGE_REPLACED));
+ Assert.assertTrue(packageInstallTrackerIntent
+ .hasAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH));
+ Assert.assertTrue(packageInstallTrackerIntent.hasAction(Intent.ACTION_PACKAGE_RESTARTED));
+ Assert.assertTrue(packageInstallTrackerIntent
+ .hasDataScheme(RegisteredServicesCache.PACKAGE_DATA));
+ IntentFilter sdCardIntentFilter = mIntentFilterArgumentCaptor.getAllValues().get(1);
+ Assert.assertTrue(sdCardIntentFilter
+ .hasAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE));
+ Assert.assertTrue(sdCardIntentFilter
+ .hasAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE));
+ Assert.assertEquals(mRegisteredServicesCache.mReceiver.get(),
+ mReceiverArgumentCaptor.getAllValues().get(0));
+ Assert.assertEquals(mRegisteredServicesCache.mReceiver.get(),
+ mReceiverArgumentCaptor.getAllValues().get(1));
+ verify(mContext, times(2)).getFilesDir();
+ // Verify that correct file setting directories are set
+ Assert.assertEquals(mRegisteredServicesCache.mDynamicSettingsFile.getBaseFile()
+ .getParentFile(), DIR);
+ Assert.assertEquals(mRegisteredServicesCache.mDynamicSettingsFile.getBaseFile()
+ .getAbsolutePath(), DIR + RegisteredServicesCache.AID_XML_PATH);
+ Assert.assertEquals(mRegisteredServicesCache.mOthersFile.getBaseFile().getParentFile(),
+ DIR);
+ Assert.assertEquals(mRegisteredServicesCache.mOthersFile.getBaseFile()
+ .getAbsolutePath(), DIR + RegisteredServicesCache.OTHER_STATUS_PATH);
+ }
+
+ @Test
+ public void testInitialize_filesExist() throws IOException,
+ PackageManager.NameNotFoundException {
+ mRegisteredServicesCache = new RegisteredServicesCache(mContext, mCallback,
+ mDynamicSettingsFile, mOtherSettingsFile, mServiceParser);
+ InputStream dynamicSettingsIs = InstrumentationRegistry.getInstrumentation()
+ .getContext().getResources().getAssets()
+ .open(RegisteredServicesCache.AID_XML_PATH);
+ InputStream otherSettingsIs = InstrumentationRegistry.getInstrumentation()
+ .getContext().getResources().getAssets()
+ .open(RegisteredServicesCache.OTHER_STATUS_PATH);
+ when(mDynamicSettingsFile.openRead()).thenReturn(dynamicSettingsIs);
+ when(mOtherSettingsFile.openRead()).thenReturn(otherSettingsIs);
+ when(mDynamicSettingsFile.exists()).thenReturn(true);
+ when(mOtherSettingsFile.exists()).thenReturn(true);
+
+ mRegisteredServicesCache.initialize();
+
+ // Verify that file operations are called
+ verify(mDynamicSettingsFile).exists();
+ verify(mOtherSettingsFile).exists();
+ verify(mDynamicSettingsFile).openRead();
+ verify(mOtherSettingsFile).openRead();
+ // Verify that user services are read properly
+ Assert.assertEquals(1, mRegisteredServicesCache.mUserServices.size());
+ RegisteredServicesCache.UserServices userServices
+ = mRegisteredServicesCache.mUserServices.get(USER_ID);
+ Assert.assertEquals(2, userServices.services.size());
+ Assert.assertTrue(userServices.services.containsKey(WALLET_HOLDER_SERVICE_COMPONENT));
+ Assert.assertTrue(userServices.services.containsKey(ANOTHER_SERVICE_COMPONENT));
+ Assert.assertEquals(3, userServices.dynamicSettings.size());
+ // Verify that dynamic settings are read properly
+ Assert.assertTrue(userServices.dynamicSettings
+ .containsKey(WALLET_HOLDER_SERVICE_COMPONENT));
+ Assert.assertTrue(userServices.dynamicSettings.containsKey(NON_PAYMENT_SERVICE_COMPONENT));
+ // Verify that dynamic settings are properly populated for each service in the xml
+ // Verify the details of service 1
+ RegisteredServicesCache.DynamicSettings walletHolderSettings =
+ userServices.dynamicSettings.get(WALLET_HOLDER_SERVICE_COMPONENT);
+ Assert.assertEquals(OFFHOST_SE_STRING+"1", walletHolderSettings.offHostSE);
+ Assert.assertEquals(1, walletHolderSettings.uid);
+ Assert.assertEquals(TRUE_STRING, walletHolderSettings.shouldDefaultToObserveModeStr);
+ Assert.assertTrue(walletHolderSettings.aidGroups
+ .containsKey(CardEmulation.CATEGORY_PAYMENT));
+ Assert.assertTrue(walletHolderSettings.aidGroups.get(CardEmulation.CATEGORY_PAYMENT)
+ .getAids().containsAll(PAYMENT_AIDS));
+ Assert.assertFalse(walletHolderSettings.aidGroups
+ .containsKey(CardEmulation.CATEGORY_OTHER));
+ // Verify the details of service 2
+ RegisteredServicesCache.DynamicSettings nonPaymentSettings =
+ userServices.dynamicSettings.get(NON_PAYMENT_SERVICE_COMPONENT);
+ Assert.assertEquals(OFFHOST_SE_STRING+"2", nonPaymentSettings.offHostSE);
+ Assert.assertEquals(1, nonPaymentSettings.uid);
+ Assert.assertEquals(FALSE_STRING, nonPaymentSettings.shouldDefaultToObserveModeStr);
+ Assert.assertTrue(nonPaymentSettings.aidGroups
+ .containsKey(CardEmulation.CATEGORY_OTHER));
+ Assert.assertTrue(nonPaymentSettings.aidGroups.get(CardEmulation.CATEGORY_OTHER)
+ .getAids().containsAll(NON_PAYMENT_AID));
+ // Verify that other settings are read properly
+ Assert.assertEquals(1, userServices.others.size());
+ Assert.assertTrue(userServices.others.containsKey(ANOTHER_SERVICE_COMPONENT));
+ RegisteredServicesCache.OtherServiceStatus otherServiceStatus
+ = userServices.others.get(ANOTHER_SERVICE_COMPONENT);
+ Assert.assertTrue(otherServiceStatus.checked);
+ Assert.assertEquals(1, otherServiceStatus.uid);
+ // Verify that the installed services are populated properly
+ verify(mContext)
+ .createPackageContextAsUser(eq(ANDROID_STRING), eq(0), eq(USER_HANDLE));
+ verify(mContext).getPackageManager();
+ verify(mPackageManager, times(2))
+ .queryIntentServicesAsUser(mIntentArgumentCaptor.capture(),
+ mFlagArgumentCaptor.capture(), eq(USER_HANDLE));
+ Intent onHostIntent = mIntentArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(HostApduService.SERVICE_INTERFACE, onHostIntent.getAction());
+ Intent offHostIntent = mIntentArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(OffHostApduService.SERVICE_INTERFACE, offHostIntent.getAction());
+ PackageManager.ResolveInfoFlags onHostFlag = mFlagArgumentCaptor.getAllValues().get(0);
+ Assert.assertEquals(PackageManager.GET_META_DATA, onHostFlag.getValue());
+ PackageManager.ResolveInfoFlags offHostFlag = mFlagArgumentCaptor.getAllValues().get(1);
+ Assert.assertEquals(PackageManager.GET_META_DATA, offHostFlag.getValue());
+ // Verify that the installed services are filtered properly
+ verify(mPackageManager).checkPermission(eq(android.Manifest.permission.NFC),
+ eq(WALLET_HOLDER_PACKAGE_NAME));
+ verify(mPackageManager).checkPermission(eq(android.Manifest.permission.NFC),
+ eq(NON_PAYMENT_NFC_PACKAGE_NAME));
+ verify(mPackageManager).checkPermission(eq(android.Manifest.permission.NFC),
+ eq(ANOTHER_PACKAGE_NAME));
+ // Verify that the callback is called with properly installed and filtered services.
+ verify(mCallback).onServicesUpdated(eq(USER_ID), mApduServiceListCaptor.capture(),
+ eq(false));
+ List<ApduServiceInfo> apduServiceInfos = mApduServiceListCaptor.getValue();
+ Assert.assertEquals(2, apduServiceInfos.size());
+ Assert.assertEquals(WALLET_HOLDER_SERVICE_COMPONENT, apduServiceInfos.get(0)
+ .getComponent());
+ Assert.assertEquals(ANOTHER_SERVICE_COMPONENT, apduServiceInfos.get(1).getComponent());
+ }
+
+ private ResolveInfo createServiceResolveInfo(boolean hasPermission,
+ String packageName, String className,
+ List<String> categories) {
+ when(mPackageManager.checkPermission(any(), eq(packageName)))
+ .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+ : PackageManager.PERMISSION_DENIED);
+ ApduServiceInfo apduServiceInfo = Mockito.mock(ApduServiceInfo.class);
+ when(apduServiceInfo.getComponent()).thenReturn(new ComponentName(packageName, className));
+ if (!categories.isEmpty()) {
+ for(String category : categories) {
+ when(apduServiceInfo.hasCategory(category)).thenReturn(true);
+ }
+ }
+ mMappedServices.put(className, apduServiceInfo);
+
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.permission = android.Manifest.permission.BIND_NFC_SERVICE;
+ resolveInfo.serviceInfo.exported = true;
+ resolveInfo.serviceInfo.packageName = packageName;
+ resolveInfo.serviceInfo.name = className;
+ return resolveInfo;
+ }
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/WalletRoleObserverTest.java b/tests/unit/src/com/android/nfc/cardemulation/WalletRoleObserverTest.java
new file mode 100644
index 0000000..04f42b7
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/WalletRoleObserverTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 com.android.nfc.cardemulation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class WalletRoleObserverTest {
+
+ private static final int USER_ID = 1;
+ private static final UserHandle USER_HANDLE = UserHandle.of(USER_ID);
+
+ private static final String WALLET_ROLE_HOLDER = "gms.wallet.stuff";
+
+ WalletRoleObserver mWalletRoleObserver;
+ @Mock
+ Context mContext;
+ @Mock
+ RoleManager mRoleManager;
+ @Mock
+ WalletRoleObserver.Callback mCallback;
+ @Mock
+ Executor mExecutor;
+ @Captor
+ ArgumentCaptor<String> mRoleNameCaptor;
+ @Captor
+ ArgumentCaptor<UserHandle> mUserHandlerCaptor;
+ @Captor
+ ArgumentCaptor<Executor> mExecutorCaptor;
+ @Captor
+ ArgumentCaptor<String> mRoleHolderCaptor;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getMainExecutor()).thenReturn(mExecutor);
+ mWalletRoleObserver = new WalletRoleObserver(mContext, mRoleManager, mCallback);
+ }
+
+ @Test
+ public void testConstructor() {
+ verify(mContext).getMainExecutor();
+ verify(mRoleManager).addOnRoleHoldersChangedListenerAsUser(mExecutorCaptor.capture(),any(),
+ mUserHandlerCaptor.capture());
+ Assert.assertEquals(mExecutor, mExecutorCaptor.getValue());
+ Assert.assertEquals(UserHandle.ALL, mUserHandlerCaptor.getValue());
+ }
+
+ @Test
+ public void testGetDefaultWalletRoleHolder_roleAvailable_returnsTheHolder() {
+ List<String> roleHolders = ImmutableList.of(WALLET_ROLE_HOLDER);
+ when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(true);
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_WALLET),
+ eq(USER_HANDLE))).thenReturn(roleHolders);
+
+ String roleHolder = mWalletRoleObserver.getDefaultWalletRoleHolder(USER_ID);
+
+ verify(mRoleManager).isRoleAvailable(mRoleNameCaptor.capture());
+ verify(mRoleManager).getRoleHoldersAsUser(mRoleNameCaptor.capture(),
+ mUserHandlerCaptor.capture());
+ Assert.assertEquals(roleHolder, WALLET_ROLE_HOLDER);
+ Assert.assertEquals(RoleManager.ROLE_WALLET, mRoleNameCaptor.getAllValues().get(0));
+ Assert.assertEquals(RoleManager.ROLE_WALLET, mRoleNameCaptor.getAllValues().get(1));
+ }
+
+ @Test
+ public void testGetDefaultWalletRoleHolder_roleNotAvailable_returnsNull() {
+ when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(false);
+
+ String roleHolder = mWalletRoleObserver.getDefaultWalletRoleHolder(USER_ID);
+
+ Assert.assertNull(roleHolder);
+ }
+
+ @Test
+ public void testCallbackFiringOnRoleChange_roleWallet() {
+ List<String> roleHolders = ImmutableList.of(WALLET_ROLE_HOLDER);
+ when(mRoleManager.getRoleHolders(eq(RoleManager.ROLE_WALLET))).thenReturn(roleHolders);
+ mWalletRoleObserver.mOnRoleHoldersChangedListener
+ .onRoleHoldersChanged(RoleManager.ROLE_WALLET, USER_HANDLE);
+
+ verify(mRoleManager).getRoleHolders(mRoleNameCaptor.capture());
+ verify(mCallback).onWalletRoleHolderChanged(mRoleHolderCaptor.capture(), eq(USER_ID));
+ Assert.assertEquals(RoleManager.ROLE_WALLET, mRoleNameCaptor.getValue());
+ Assert.assertEquals(WALLET_ROLE_HOLDER, mRoleHolderCaptor.getValue());
+ }
+
+ @Test
+ public void testCallbackNotFiringOnRoleChange_roleNonWallet() {
+ mWalletRoleObserver.mOnRoleHoldersChangedListener
+ .onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT, USER_HANDLE);
+
+ verifyZeroInteractions(mCallback);
+ }
+
+ @Test
+ public void testOnUserSwitched_callsCallback() {
+ List<String> roleHolders = ImmutableList.of(WALLET_ROLE_HOLDER);
+ when(mRoleManager.isRoleAvailable(eq(RoleManager.ROLE_WALLET))).thenReturn(true);
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_WALLET),
+ eq(USER_HANDLE))).thenReturn(roleHolders);
+
+ mWalletRoleObserver.onUserSwitched(USER_ID);
+ verify(mRoleManager).isRoleAvailable(mRoleNameCaptor.capture());
+ verify(mRoleManager).getRoleHoldersAsUser(mRoleNameCaptor.capture(),
+ mUserHandlerCaptor.capture());
+ verify(mCallback).onWalletRoleHolderChanged(mRoleHolderCaptor.capture(), eq(USER_ID));
+ Assert.assertEquals(WALLET_ROLE_HOLDER, mRoleHolderCaptor.getValue());
+ Assert.assertEquals(RoleManager.ROLE_WALLET, mRoleNameCaptor.getAllValues().get(0));
+ Assert.assertEquals(RoleManager.ROLE_WALLET, mRoleNameCaptor.getAllValues().get(1));
+ }
+
+}
diff --git a/tests/unit/src/com/android/nfc/cardemulation/util/StatsdUtilsTest.java b/tests/unit/src/com/android/nfc/cardemulation/util/StatsdUtilsTest.java
new file mode 100644
index 0000000..6744b2e
--- /dev/null
+++ b/tests/unit/src/com/android/nfc/cardemulation/util/StatsdUtilsTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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 com.android.nfc.cardemulation.util;
+
+import static android.nfc.cardemulation.PollingFrame.POLLING_LOOP_TYPE_UNKNOWN;
+
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1;
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2;
+import static com.android.nfc.NfcStatsLog.NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.nfc.cardemulation.PollingFrame;
+import android.os.Bundle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import java.util.HexFormat;
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public class StatsdUtilsTest {
+ private final StatsdUtils mStatsdUtils = spy(new StatsdUtils());
+
+ @Test
+ public void testGetFrameType() {
+ assertThat(StatsdUtils.getFrameType(ECP_V1_PAYMENT)).isEqualTo(
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V1);
+
+ assertThat(StatsdUtils.getFrameType(ECP_V2_TRANSIT_MBTA)).isEqualTo(
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__ECP_V2);
+
+ assertThat(StatsdUtils.getFrameType(UNKNOWN_FRAME)).isEqualTo(
+ NFC_POLLING_LOOP_NOTIFICATION_REPORTED__PROPRIETARY_FRAME_TYPE__PROPRIETARY_FRAME_UNKNOWN);
+ }
+
+ @Test
+ public void testLogPollingFrame_ecp1Once() {
+ PollingFrame frameData =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, ECP_V1_PAYMENT, -1, 0, false);
+
+ mStatsdUtils.tallyPollingFrame(ECP_V1_PAYMENT_KEY, frameData);
+ mStatsdUtils.logPollingFrames();
+
+ StatsdUtils.PollingFrameLog expectedFrame = new StatsdUtils.PollingFrameLog(ECP_V1_PAYMENT);
+ expectedFrame.repeatCount = 1;
+
+ verify(mStatsdUtils).writeToStatsd(expectedFrame);
+ }
+
+ @Test
+ public void testLogPollingFrame_ecp1TwiceInTwoWrites() {
+ PollingFrame frameData =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, ECP_V1_PAYMENT, -1, 0, false);
+
+ mStatsdUtils.tallyPollingFrame(ECP_V1_PAYMENT_KEY, frameData);
+ mStatsdUtils.logPollingFrames();
+ mStatsdUtils.tallyPollingFrame(ECP_V1_PAYMENT_KEY, frameData);
+ mStatsdUtils.logPollingFrames();
+
+ StatsdUtils.PollingFrameLog expectedFrame = new StatsdUtils.PollingFrameLog(ECP_V1_PAYMENT);
+ expectedFrame.repeatCount = 1;
+
+ verify(mStatsdUtils, times(2)).tallyPollingFrame(any(), any());
+ verify(mStatsdUtils, times(2)).logPollingFrames();
+ verify(mStatsdUtils, times(2)).writeToStatsd(expectedFrame);
+ verifyNoMoreInteractions(mStatsdUtils);
+ }
+
+ @Test
+ public void testLogPollingFrame_ecp2Repeated() {
+ PollingFrame frameData =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, ECP_V2_TRANSIT_MBTA, -1, 0, false);
+
+ mStatsdUtils.tallyPollingFrame(ECP_V2_TRANSIT_MBTA_KEY, frameData);
+ mStatsdUtils.tallyPollingFrame(ECP_V2_TRANSIT_MBTA_KEY, frameData);
+ mStatsdUtils.tallyPollingFrame(ECP_V2_TRANSIT_MBTA_KEY, frameData);
+
+ mStatsdUtils.logPollingFrames();
+
+ StatsdUtils.PollingFrameLog expectedFrame =
+ new StatsdUtils.PollingFrameLog(ECP_V2_TRANSIT_MBTA);
+ expectedFrame.repeatCount = 3;
+ verify(mStatsdUtils).writeToStatsd(expectedFrame);
+ }
+
+ @Test
+ public void testLogPollingFrame_ecp2RepeatedTwoTypes() {
+ PollingFrame frame1Data =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, UNKNOWN_FRAME, -1, 0, false);
+
+ PollingFrame frame2Data =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, ECP_V2_TRANSIT_MBTA, -1, 0, false);
+
+ mStatsdUtils.tallyPollingFrame(UNKNOWN_FRAME_KEY, frame1Data);
+ mStatsdUtils.tallyPollingFrame(ECP_V2_TRANSIT_MBTA_KEY, frame2Data);
+ mStatsdUtils.tallyPollingFrame(UNKNOWN_FRAME_KEY, frame1Data);
+ mStatsdUtils.logPollingFrames();
+
+ StatsdUtils.PollingFrameLog expectedFrame = new StatsdUtils.PollingFrameLog(UNKNOWN_FRAME);
+ expectedFrame.repeatCount = 2;
+ verify(mStatsdUtils).writeToStatsd(expectedFrame);
+
+ expectedFrame = new StatsdUtils.PollingFrameLog(ECP_V2_TRANSIT_MBTA);
+ expectedFrame.repeatCount = 1;
+
+ verify(mStatsdUtils, times(3)).tallyPollingFrame(any(), any());
+ verify(mStatsdUtils).logPollingFrames();
+ verify(mStatsdUtils).writeToStatsd(expectedFrame);
+ verifyNoMoreInteractions(mStatsdUtils);
+ }
+
+ @Test
+ public void testFieldGain() {
+ PollingFrame frame1Data =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, UNKNOWN_FRAME, GAIN_1, 0, false);
+
+ PollingFrame frame2Data =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, UNKNOWN_FRAME, GAIN_1, 0, false);
+
+ PollingFrame frame3Data =
+ new PollingFrame(POLLING_LOOP_TYPE_UNKNOWN, UNKNOWN_FRAME, GAIN_2, 0, false);
+
+ mStatsdUtils.tallyPollingFrame(UNKNOWN_FRAME_KEY, frame1Data);
+ mStatsdUtils.tallyPollingFrame(UNKNOWN_FRAME_KEY, frame2Data);
+ mStatsdUtils.tallyPollingFrame(UNKNOWN_FRAME_KEY, frame3Data);
+ mStatsdUtils.logPollingFrames();
+
+ verify(mStatsdUtils, times(3)).tallyPollingFrame(any(), any());
+ verify(mStatsdUtils).logPollingFrames();
+ verify(mStatsdUtils, times(1)).logFieldChanged(true, GAIN_1);
+ verify(mStatsdUtils, times(1)).logFieldChanged(true, GAIN_2);
+ verify(mStatsdUtils).writeToStatsd(any());
+ verifyNoMoreInteractions(mStatsdUtils);
+ }
+
+
+ private static final int GAIN_1 = 42;
+ private static final int GAIN_2 = 25;
+ private static final byte[] ECP_V1_PAYMENT = new byte[]{0x6a, 0x01, 0x00, 0x00, 0x00};
+ private static final String ECP_V1_PAYMENT_KEY =
+ HexFormat.of().formatHex(ECP_V1_PAYMENT).toUpperCase(Locale.ROOT);
+ private static final byte[] ECP_V2_TRANSIT_MBTA =
+ new byte[]{0x6a, 0x02, (byte) 0xc8, 0x01, 0x00, 0x03, 0x00, 0x03, 0x7f, 0x00, 0x00,
+ 0x00, 0x00, 0x71, (byte) 0xe7};
+ private static final String ECP_V2_TRANSIT_MBTA_KEY =
+ HexFormat.of().formatHex(ECP_V2_TRANSIT_MBTA).toUpperCase(Locale.ROOT);
+
+ private static final byte[] UNKNOWN_FRAME =
+ new byte[]{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x41, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+ 0x21};
+ private static final String UNKNOWN_FRAME_KEY =
+ HexFormat.of().formatHex(UNKNOWN_FRAME).toUpperCase(Locale.ROOT);
+}