Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d7406f71 authored by Ze Li's avatar Ze Li
Browse files

Use the callback of CachedBluetoothDevice instead of metadata change listener...

Use the callback of CachedBluetoothDevice instead of metadata change listener to update summary in qs tile dialog.

If the battery data source of the device is Bluetooth battery service instead of metadata, only registering the metadata change listener can not receive battery update. Using the callback of CachedBluetoothDevice can solve this bug.

Test: com.android.systemui.bluetooth.qsdialog.BluetoothDeviceMetadataInteractorTest
Bug: 397847825
Flag: com.android.settingslib.flags.refactor_battery_level_display
Change-Id: I261d973a2b85edae467b32ee01c4e9ad7ab78789
parent 68da9a90
Loading
Loading
Loading
Loading
+110 −0
Original line number Diff line number Diff line
@@ -19,11 +19,14 @@ package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.graphics.drawable.Drawable
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper
import android.util.Pair
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags.FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY
import com.android.systemui.SysuiTestCase
import com.android.systemui.bluetooth.bluetoothAdapter
import com.android.systemui.coroutines.collectLastValue
@@ -31,6 +34,7 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -67,6 +71,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    @Mock private lateinit var drawable: Drawable
    @Captor
    private lateinit var argumentCaptor: ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
    @Captor private lateinit var callbackCaptor: ArgumentCaptor<CachedBluetoothDevice.Callback>
    private lateinit var interactor: BluetoothDeviceMetadataInteractor

    @Before
@@ -93,6 +98,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdateEmpty_doNothing() {
        with(kosmos) {
            testScope.runTest {
@@ -108,6 +114,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_registerListener() {
        with(kosmos) {
            testScope.runTest {
@@ -125,6 +132,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_sameDeviceItems_registerListenerOnce() {
        with(kosmos) {
            testScope.runTest {
@@ -143,6 +151,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_differentDeviceItems_unregisterOldAndRegisterNew() {
        with(kosmos) {
            testScope.runTest {
@@ -165,6 +174,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun metadataUpdate_triggerCallback_emit() {
        with(kosmos) {
            testScope.runTest {
@@ -193,6 +203,7 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
    }

    @Test
    @DisableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun metadataUpdate_triggerCallbackNonBatteryKey_doNothing() {
        with(kosmos) {
            testScope.runTest {
@@ -221,6 +232,105 @@ class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
        }
    }

    @Test
    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdateEmpty_doNotRegisterCallback() {
        with(kosmos) {
            testScope.runTest {
                val update by collectLastValue(interactor.metadataUpdate)
                deviceItemUpdate.emit(emptyList())
                runCurrent()

                assertThat(update).isNull()
                verify(cachedDevice1, never()).registerCallback(any(), any())
                verify(cachedDevice1, never()).unregisterCallback(any())
                verify(cachedDevice2, never()).registerCallback(any(), any())
                verify(cachedDevice2, never()).unregisterCallback(any())
            }
        }
    }

    @Test
    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_registerCallback() {
        with(kosmos) {
            testScope.runTest {
                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
                val update by collectLastValue(interactor.metadataUpdate)
                deviceItemUpdate.emit(listOf(deviceItem))
                runCurrent()

                assertThat(update).isNull()
                verify(cachedDevice1).registerCallback(any(), any())
                verify(cachedDevice1, never()).unregisterCallback(any())
            }
        }
    }

    @Test
    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_sameDeviceItems_registerCallbackOnce() {
        with(kosmos) {
            testScope.runTest {
                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
                val update by collectLastValue(interactor.metadataUpdate)
                deviceItemUpdate.emit(listOf(deviceItem))
                deviceItemUpdate.emit(listOf(deviceItem))
                runCurrent()

                assertThat(update).isNull()
                verify(cachedDevice1, times(1)).registerCallback(any(), any())
                verify(cachedDevice1, never()).unregisterCallback(any())
            }
        }
    }

    @Test
    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun deviceItemUpdate_differentDeviceItems_unregisterOldAndRegisterNewCallback() {
        with(kosmos) {
            testScope.runTest {
                val deviceItem1 = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
                val deviceItem2 = AvailableMediaDeviceItemFactory().create(context, cachedDevice2)
                val update by collectLastValue(interactor.metadataUpdate)
                deviceItemUpdate.emit(listOf(deviceItem1))
                deviceItemUpdate.emit(listOf(deviceItem1, deviceItem2))
                runCurrent()

                assertThat(update).isNull()
                verify(cachedDevice1, times(2)).registerCallback(any(), any())
                verify(cachedDevice2, times(1)).registerCallback(any(), any())
                verify(cachedDevice1, times(1)).unregisterCallback(any())
            }
        }
    }

    @Test
    @OptIn(ExperimentalCoroutinesApi::class)
    @EnableFlags(FLAG_REFACTOR_BATTERY_LEVEL_DISPLAY)
    fun callbackTriggered_emit() {
        with(kosmos) {
            testScope.runTest {
                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
                val update by collectLastValue(interactor.metadataUpdate)
                deviceItemUpdate.emit(listOf(deviceItem))
                runCurrent()

                assertThat(update).isNull()
                verify(cachedDevice1).registerCallback(any(), callbackCaptor.capture())

                val callback = callbackCaptor.value
                callback.onDeviceAttributesChanged()

                assertThat(update).isEqualTo(Unit)
            }
        }
    }

    companion object {
        private const val DEVICE_NAME = "DeviceName"
        private const val CONNECTION_SUMMARY = "ConnectionSummary"
+58 −24
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.bluetooth.qsdialog

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags.refactorBatteryLevelDisplay
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -59,23 +61,49 @@ constructor(
                bluetoothAdapter?.addOnMetadataChangedListener(
                    bluetoothDevice,
                    executor,
                metadataChangedListener
                    metadataChangedListener,
                )
                awaitClose {
                    bluetoothAdapter?.removeOnMetadataChangedListener(
                        bluetoothDevice,
                    metadataChangedListener
                        metadataChangedListener,
                    )
                }
            }
            .flowOn(backgroundDispatcher)

    private fun callbackUpdateForCachedBluetoothDevice(
        cachedBluetoothDevice: CachedBluetoothDevice
    ): Flow<Unit> =
        conflatedCallbackFlow {
                val attributesChangedCallback =
                    CachedBluetoothDevice.Callback {
                        trySendWithFailureLogging(Unit, TAG, "onAttributesChanged")
                        logger.logAttributesChanged(cachedBluetoothDevice.address)
                    }
                cachedBluetoothDevice.registerCallback(executor, attributesChangedCallback)
                awaitClose { cachedBluetoothDevice.unregisterCallback(attributesChangedCallback) }
            }
            .flowOn(backgroundDispatcher)

    val metadataUpdate: Flow<Unit> =
        if (refactorBatteryLevelDisplay()) {
            deviceItemInteractor.deviceItemUpdate
                .distinctUntilChangedBy { it.cachedBluetoothDevices }
                .flatMapLatest { items ->
                    items.cachedBluetoothDevices
                        .map { cachedBluetoothDevice ->
                            callbackUpdateForCachedBluetoothDevice(cachedBluetoothDevice)
                        }
                        .merge()
                }
        } else {
            deviceItemInteractor.deviceItemUpdate
                .distinctUntilChangedBy { it.bluetoothDevices }
                .flatMapLatest { items ->
                    items.bluetoothDevices.map { device -> metadataUpdateForDevice(device) }.merge()
                }
            .flowOn(backgroundDispatcher)
        }

    private companion object {
        private const val TAG = "BluetoothDeviceMetadataInteractor"
@@ -85,5 +113,11 @@ constructor(
                    listOf(item.cachedBluetoothDevice.device) +
                        item.cachedBluetoothDevice.memberDevice.map { it.device }
                }

        private val List<DeviceItem>.cachedBluetoothDevices: Set<CachedBluetoothDevice>
            get() =
                flatMapTo(mutableSetOf()) { item ->
                    listOf(item.cachedBluetoothDevice) + item.cachedBluetoothDevice.memberDevice
                }
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -113,6 +113,9 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
            { "BatteryChanged. address=$str1 key=$int1 value=$str2" },
        )

    fun logAttributesChanged(address: String) =
        logBuffer.log(TAG, DEBUG, { str1 = address }, { "AttributesChanged. address=$str1" })

    fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
        logBuffer.log(
            TAG,