Implement polling loop filter register to route custom polling loop
annotations to services that can handle them.
Bug: b/294217286
Test: Tested with new CTS tests run manually
Change-Id: I68f988369ee3a801873863c0118921e0caad365f
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e0c58d5..bd024bd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -9984,6 +9984,7 @@
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -9994,6 +9995,7 @@
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
@@ -10006,6 +10008,7 @@
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5cfb1a3..2eb28eb 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4372,6 +4372,14 @@
<attr name="name" />
</declare-styleable>
+ <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
+ <code>host-apdu-service</code> to indicate polling loop frames that
+ your service can handle. -->
+ <declare-styleable name="PollingLoopFilter">
+ <!-- The polling loop frame. This attribute is mandatory. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
describes an {@link android.nfc.cardemulation.HostNfcFService} service, which
is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 24c145f..7573474 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -204,6 +204,7 @@
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeAidsForService(android.content.ComponentName, String);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 1faaf1b..bec62c5 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -99,4 +99,5 @@
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
void notifyPollingLoop(in Bundle frame);
+ void notifyHceDeactivated();
}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index f4b4604..791bd8c 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -32,6 +32,7 @@
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
boolean unsetOffHostForService(int userHandle, in ComponentName service);
AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index d50a9a2..68c16e6 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2783,7 +2783,33 @@
}
}
-
+ /**
+ * Notifies the system of a an HCE session being deactivated.
+ * *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void notifyHceDeactivated() {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ sService.notifyHceDeactivated();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return;
+ }
+ try {
+ sService.notifyHceDeactivated();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ }
/**
* Sets NFC charging feature.
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 41dee3a..426c5aa 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
@@ -102,6 +103,8 @@
*/
private final HashMap<String, AidGroup> mDynamicAidGroups;
+ private final ArrayList<String> mPollingLoopFilters;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -169,6 +172,7 @@
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+ this.mPollingLoopFilters = new ArrayList<String>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -282,6 +286,7 @@
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
+ mPollingLoopFilters = new ArrayList<String>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -364,6 +369,15 @@
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG
+ && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.PollingLoopFilter);
+ String plf =
+ a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+ .toUpperCase(Locale.ROOT);
+ mPollingLoopFilters.add(plf);
+ a.recycle();
}
}
} catch (NameNotFoundException e) {
@@ -420,6 +434,16 @@
}
/**
+ * Returns the current polling loop filters for this service.
+ * @return List of polling loop filters.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ @NonNull
+ public List<String> getPollingLoopFilters() {
+ return mPollingLoopFilters;
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -596,6 +620,26 @@
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+ * delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
+ * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+ * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
* Sets the off host Secure Element.
* @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
* Ref: GSMA TS.26 - NFC Handset Requirements
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 81eab71..0943392 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -57,6 +57,8 @@
*/
public final class CardEmulation {
private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
+
static final String TAG = "CardEmulation";
/**
@@ -353,6 +355,34 @@
}
/**
+ * Register a polling loop filter for a HostApduService.
+ * @param service The HostApduService to register the filter for.
+ * @param pollingLoopFilter The filter to register.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
+ @NonNull String pollingLoopFilter) {
+ try {
+ return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
+ service, pollingLoopFilter);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerPollingLoopFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
* Registers a list of AIDs for a specific category for the
* specified service.
*
@@ -933,6 +963,24 @@
}
/**
+ * Tests the validity of the polling loop filter.
+ * @param pollingLoopFilter The polling loop filter to test.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ // Verify hex characters
+ if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
+ Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
+ + " is not a valid Polling Loop Filter.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* A valid AID according to ISO/IEC 7816-4:
* <ul>
* <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)