Merge "Add CDM system api to check for association presence"
diff --git a/api/system-current.txt b/api/system-current.txt
index fd8fd88..e963560 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1371,6 +1371,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 076d32e..466320b 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -643,6 +643,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 8a735c7..b357b3e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3553,7 +3553,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();