Add CDM system api to check for association presence

Per agreement with wifi team, wifi stack will call into CDM when wifi
connection is requested, treating CDM record similarly to the previously
approved networks records, i.e. skipping UI when association exists.

Test: atest android.os.cts.CompanionDeviceManagerTest
Fixes: 143572863
Change-Id: I28b44076e594fb9357b5a543176d285414ef06f8
diff --git a/api/system-current.txt b/api/system-current.txt
index e6a3e9b..e525a71 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1370,6 +1370,14 @@
 
 }
 
+package android.companion {
+
+  public final class CompanionDeviceManager {
+    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+  }
+
+}
+
 package android.content {
 
   public class ContentProviderClient implements java.lang.AutoCloseable {
diff --git a/api/test-current.txt b/api/test-current.txt
index a060dfc..5247bd1 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -640,6 +640,14 @@
 
 }
 
+package android.companion {
+
+  public final class CompanionDeviceManager {
+    method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public boolean isDeviceAssociated(@NonNull String, @NonNull android.net.MacAddress, @NonNull android.os.UserHandle);
+  }
+
+}
+
 package android.content {
 
   public final class AutofillOptions implements android.os.Parcelable {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 9cb73f9..28cc1f8 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -21,7 +21,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.Application;
 import android.app.PendingIntent;
@@ -29,9 +32,11 @@
 import android.content.Context;
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
+import android.net.MacAddress;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 
@@ -252,6 +257,38 @@
         }
     }
 
+    /**
+     * Check if a given package was {@link #associate associated} with a device with given
+     * mac address by given user.
+     *
+     * @param packageName the package to check for
+     * @param macAddress the mac address or BSSID of the device to check for
+     * @param user the user to check for
+     * @return whether a corresponding association record exists
+     *
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
+    public boolean isDeviceAssociated(
+            @NonNull String packageName,
+            @NonNull MacAddress macAddress,
+            @NonNull UserHandle user) {
+        if (!checkFeaturePresent()) {
+            return false;
+        }
+        checkNotNull(packageName, "package name cannot be null");
+        checkNotNull(macAddress, "mac address cannot be null");
+        checkNotNull(user, "user cannot be null");
+        try {
+            return mService.isDeviceAssociated(
+                    packageName, macAddress.toString(), user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 561342e..2e1ff0b 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -39,4 +39,6 @@
 
     boolean hasNotificationAccess(in ComponentName component);
     PendingIntent requestNotificationAccess(in ComponentName component);
+
+    boolean isDeviceAssociated(in String packageName, in String macAddress, int userId);
 }
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f9cf23b..4165f20 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -231,6 +231,15 @@
     }
 
     /**
+     * Returns whether there exists at least one element in the set for which
+     * condition {@code predicate} is true
+     */
+    public static <T> boolean any(@Nullable Set<T> items,
+            java.util.function.Predicate<T> predicate) {
+        return find(items, predicate) != null;
+    }
+
+    /**
      * Returns the first element from the list for which
      * condition {@code predicate} is true, or null if there is no such element
      */
@@ -245,6 +254,37 @@
     }
 
     /**
+     * Returns the first element from the set for which
+     * condition {@code predicate} is true, or null if there is no such element
+     */
+    public static @Nullable <T> T find(@Nullable Set<T> cur,
+            java.util.function.Predicate<T> predicate) {
+        if (cur == null || predicate == null) return null;
+        int size = cur.size();
+        if (size == 0) return null;
+        try {
+            if (cur instanceof ArraySet) {
+                ArraySet<T> arraySet = (ArraySet<T>) cur;
+                for (int i = 0; i < size; i++) {
+                    T item = arraySet.valueAt(i);
+                    if (predicate.test(item)) {
+                        return item;
+                    }
+                }
+            } else {
+                for (T t : cur) {
+                    if (predicate.test(t)) {
+                        return t;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw ExceptionUtils.propagate(e);
+        }
+        return null;
+    }
+
+    /**
      * Similar to {@link List#add}, but with support for list values of {@code null} and
      * {@link Collections#emptyList}
      */
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 11a5062..8fa6102 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3547,7 +3547,7 @@
     <!-- Allows an application to manage the companion devices.
          @hide -->
     <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|wifi" />
 
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 067becb..8f1e156 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -335,6 +335,16 @@
             return new ComponentNameSet(setting).contains(component);
         }
 
+        @Override
+        public boolean isDeviceAssociated(String packageName, String macAddress, int userId) {
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.MANAGE_COMPANION_DEVICES, "isDeviceAssociated");
+
+            return CollectionUtils.any(
+                    readAllAssociations(userId, packageName),
+                    a -> Objects.equals(a.deviceAddress, macAddress));
+        }
+
         private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
             checkCallerIsSystemOr(callingPackage);
             int userId = getCallingUserId();