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)