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/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)