Merge "[Hide DCK devices] Implementation to determine check is a Bluetooth device is exclusively managed." into main
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index f7f0673..09b1eaf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -9,6 +9,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -24,6 +25,7 @@
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import androidx.core.graphics.drawable.IconCompat;
 
@@ -31,9 +33,12 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
+import com.google.common.collect.ImmutableSet;
+
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -47,6 +52,8 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+    private static final Set<String> EXCLUSIVE_MANAGERS = ImmutableSet.of(
+            "com.google.android.gms.dck");
 
     private static ErrorListener sErrorListener;
 
@@ -647,4 +654,66 @@
     private static String generateExpressionWithTag(String tag, String value) {
         return getTagStart(tag) + value + getTagEnd(tag);
     }
+
+    /**
+     * Returns the BluetoothDevice's exclusive manager
+     * ({@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the
+     * given set, otherwise null.
+     */
+    @Nullable
+    private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) {
+        byte[] exclusiveManagerNameBytes = bluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_EXCLUSIVE_MANAGER);
+        if (exclusiveManagerNameBytes == null) {
+            Log.d(TAG, "Bluetooth device " + bluetoothDevice.getName()
+                    + " doesn't have exclusive manager");
+            return null;
+        }
+        String exclusiveManagerName = new String(exclusiveManagerNameBytes);
+        return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName
+                : null;
+    }
+
+    /**
+     * Checks if given package is installed
+     */
+    private static boolean isPackageInstalled(Context context,
+            String packageName) {
+        PackageManager packageManager = context.getPackageManager();
+        try {
+            packageManager.getPackageInfo(packageName, 0);
+            return true;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(TAG, "Package " + packageName + " is not installed");
+        }
+        return false;
+    }
+
+    /**
+     * A BluetoothDevice is exclusively managed if
+     * 1) it has field {@link BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata.
+     * 2) the exclusive manager app name is in the allowlist.
+     * 3) the exclusive manager app is installed.
+     */
+    public static boolean isExclusivelyManagedBluetoothDevice(@NonNull Context context,
+            @NonNull BluetoothDevice bluetoothDevice) {
+        String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice);
+        if (exclusiveManagerName == null) {
+            return false;
+        }
+        if (!isPackageInstalled(context, exclusiveManagerName)) {
+            return false;
+        } else {
+            Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName);
+            return true;
+        }
+    }
+
+    /**
+     * Return the allowlist for exclusive manager names.
+     */
+    @NonNull
+    public static Set<String> getExclusiveManagers() {
+        return EXCLUSIVE_MANAGERS;
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index f7ec80b..475a6d6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -24,6 +26,8 @@
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -49,6 +53,8 @@
     private BluetoothDevice mBluetoothDevice;
     @Mock
     private AudioManager mAudioManager;
+    @Mock
+    private PackageManager mPackageManager;
 
     private Context mContext;
     private static final String STRING_METADATA = "string_metadata";
@@ -59,6 +65,7 @@
     private static final String CONTROL_METADATA =
             "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA
                     + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>";
+    private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name";
 
     @Before
     public void setUp() {
@@ -372,4 +379,55 @@
         assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
                 mAudioManager)).isEqualTo(false);
     }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                null);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() {
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                FAKE_EXCLUSIVE_MANAGER_NAME.getBytes());
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse()
+            throws Exception {
+        final String exclusiveManagerName =
+                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+                        FAKE_EXCLUSIVE_MANAGER_NAME);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                exclusiveManagerName.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo(
+                exclusiveManagerName, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(false);
+    }
+
+    @Test
+    public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue()
+            throws Exception {
+        final String exclusiveManagerName =
+                BluetoothUtils.getExclusiveManagers().stream().findAny().orElse(
+                        FAKE_EXCLUSIVE_MANAGER_NAME);
+
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn(
+                exclusiveManagerName.getBytes());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0);
+
+        assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
+                mBluetoothDevice)).isEqualTo(true);
+    }
 }