[Hide DCK Device] Hide DCK BluetoothDevice from QS SysUI
Hide DCK device from Saved and Connected devices list.
Test: atest SystemUITests:com.android.systemui.qs.tiles.dialog.bluetooth.DeviceItemFactoryTest
Bug: 323298503
Bug: 324475542
Flag: ACONFIG com.android.settingslib.flags.enable_hide_exclusively_managed_bluetooth_device DEVELOPMENT
Change-Id: I828783ed7d53d9c0ef871b1697eebeb9c3cf7e8a
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 6a1ee3a..54c5a14 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -43,4 +43,11 @@
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio private broadcast sharing via QR code"
bug: "308368124"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "enable_hide_exclusively_managed_bluetooth_device"
+ namespace: "dck_framework"
+ description: "Hide exclusively managed Bluetooth devices in BT settings menu."
+ bug: "324475542"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index 1c9be0f..f13ecf3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -21,6 +21,7 @@
import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -36,6 +37,7 @@
/** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
internal abstract class DeviceItemFactory {
abstract fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean
@@ -45,6 +47,7 @@
internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
@@ -71,6 +74,7 @@
internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
@@ -99,10 +103,18 @@
internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
- return BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ context,
+ cachedDevice.getDevice()
+ ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ } else {
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ }
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -125,10 +137,18 @@
internal class SavedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
- return cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ context,
+ cachedDevice.getDevice()
+ ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ } else {
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ }
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index fcd45a6..1df496b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -133,7 +133,7 @@
bluetoothTileDialogRepository.cachedDevices
.mapNotNull { cachedDevice ->
deviceItemFactoryList
- .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) }
+ .firstOrNull { it.isFilterMatched(context, cachedDevice, audioManager) }
?.create(context, cachedDevice)
}
.sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
index 92c7326..a8cd8c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
@@ -16,10 +16,18 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
+import android.bluetooth.BluetoothDevice
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.media.AudioManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -35,19 +43,26 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class DeviceItemFactoryTest : SysuiTestCase() {
-
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ @Mock private lateinit var packageManager: PackageManager
private val availableMediaDeviceItemFactory = AvailableMediaDeviceItemFactory()
private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
private val savedDeviceItemFactory = SavedDeviceItemFactory()
+ private val audioManager = context.getSystemService(AudioManager::class.java)!!
+
@Before
fun setup() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(cachedDevice.address).thenReturn(DEVICE_ADDRESS)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+
+ context.setMockPackageManager(packageManager)
}
@Test
@@ -72,6 +87,225 @@
assertThat(deviceItem.background).isNotNull()
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_connected_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(true)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+ `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+ .thenReturn(PackageInfo())
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenThrow(PackageManager.NameNotFoundException("Test!"))
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(true)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(false)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notBonded_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+ `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+ .thenReturn(PackageInfo())
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenThrow(PackageManager.NameNotFoundException("Test!"))
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(false)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
private fun assertDeviceItem(deviceItem: DeviceItem?, deviceItemType: DeviceItemType) {
assertThat(deviceItem).isNotNull()
assertThat(deviceItem!!.type).isEqualTo(deviceItemType)
@@ -83,5 +317,7 @@
companion object {
const val DEVICE_NAME = "DeviceName"
const val CONNECTION_SUMMARY = "ConnectionSummary"
+ private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+ private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index e236f4a..ddf0b9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -279,6 +279,7 @@
): DeviceItemFactory {
return object : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
) = isFilterMatchFunc(cachedDevice)