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

Commit 57069d3c authored by chelseahao's avatar chelseahao
Browse files

Listen to metadata changed when battery info updated.

Test: atest -c com.android.systemui.bluetooth.qsdialog
Bug: 341648139
Flag: EXEMPT bug fix
Change-Id: I0f7302c9b79d55d6240b7783a688ad0c6ba5f2e9
parent aa92cb88
Loading
Loading
Loading
Loading
+91 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.bluetooth.qsdialog

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.merge

@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BluetoothDeviceMetadataInteractor
@Inject
constructor(
    deviceItemInteractor: DeviceItemInteractor,
    private val bluetoothAdapter: BluetoothAdapter?,
    private val logger: BluetoothTileDialogLogger,
    @Background private val executor: Executor,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
    private fun metadataUpdateForDevice(bluetoothDevice: BluetoothDevice): Flow<Unit> =
        conflatedCallbackFlow {
            val metadataChangedListener =
                BluetoothAdapter.OnMetadataChangedListener { device, key, value ->
                    when (key) {
                        BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
                        BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
                        BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
                        BluetoothDevice.METADATA_MAIN_BATTERY -> {
                            trySendWithFailureLogging(Unit, TAG, "onMetadataChanged")
                            logger.logBatteryChanged(device.address, key, value)
                        }
                    }
                }
            bluetoothAdapter?.addOnMetadataChangedListener(
                bluetoothDevice,
                executor,
                metadataChangedListener
            )
            awaitClose {
                bluetoothAdapter?.removeOnMetadataChangedListener(
                    bluetoothDevice,
                    metadataChangedListener
                )
            }
        }

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

    private companion object {
        private const val TAG = "BluetoothDeviceMetadataInteractor"
        private val List<DeviceItem>.bluetoothDevices: Set<BluetoothDevice>
            get() =
                flatMapTo(mutableSetOf()) { item ->
                    listOf(item.cachedBluetoothDevice.device) +
                        item.cachedBluetoothDevice.memberDevice.map { it.device }
                }
    }
}
+0 −1
Original line number Diff line number Diff line
@@ -440,7 +440,6 @@ internal constructor(

    internal companion object {
        const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L
        const val MAX_DEVICE_ITEM_ENTRY = 3
        const val ACTION_BLUETOOTH_DEVICE_DETAILS =
            "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
        const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
+12 −0
Original line number Diff line number Diff line
@@ -90,6 +90,18 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {
            { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
        )

    fun logBatteryChanged(address: String, key: Int, value: ByteArray?) =
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = address
                int1 = key
                str2 = value?.toString() ?: ""
            },
            { "BatteryChanged. address=$str1 key=$int1 value=$str2" }
        )

    fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
        logBuffer.log(
            TAG,
+1 −1
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import javax.inject.Inject

/** Repository to get CachedBluetoothDevices for the Bluetooth Dialog. */
@SysUISingleton
internal class BluetoothTileDialogRepository
class BluetoothTileDialogRepository
@Inject
constructor(
    private val localBluetoothManager: LocalBluetoothManager?,
+19 −9
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Compa
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -50,8 +49,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.produce
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -66,6 +67,7 @@ constructor(
    private val bluetoothStateInteractor: BluetoothStateInteractor,
    private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
    private val audioSharingInteractor: AudioSharingInteractor,
    private val bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    private val activityStarter: ActivityStarter,
    private val uiEventLogger: UiEventLogger,
@@ -104,8 +106,7 @@ constructor(
                    )
                controller?.let {
                    dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true)
                }
                    ?: dialog.show()
                } ?: dialog.show()

                updateDeviceItemJob = launch {
                    deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
@@ -113,15 +114,17 @@ constructor(

                // deviceItemUpdate is emitted when device item list is done fetching, update UI and
                // stop the progress bar.
                deviceItemInteractor.deviceItemUpdate
                    .onEach {
                combine(
                        deviceItemInteractor.deviceItemUpdate,
                        deviceItemInteractor.showSeeAllUpdate
                    ) { deviceItem, showSeeAll ->
                        updateDialogUiJob?.cancel()
                        updateDialogUiJob = launch {
                            dialogDelegate.apply {
                                onDeviceItemUpdated(
                                    dialog,
                                    it.take(MAX_DEVICE_ITEM_ENTRY),
                                    showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
                                    deviceItem,
                                    showSeeAll,
                                    showPairNewDevice =
                                        bluetoothStateInteractor.isBluetoothEnabled()
                                )
@@ -132,8 +135,11 @@ constructor(
                    .launchIn(this)

                // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
                // the device item list and animiate the progress bar.
                deviceItemInteractor.deviceItemUpdateRequest
                // the device item list and animate the progress bar.
                merge(
                        deviceItemInteractor.deviceItemUpdateRequest,
                        bluetoothDeviceMetadataInteractor.metadataUpdate
                    )
                    .onEach {
                        dialogDelegate.animateProgressBar(dialog, true)
                        updateDeviceItemJob?.cancel()
@@ -305,6 +311,7 @@ constructor(
    companion object {
        private const val INTERACTION_JANK_TAG = "bluetooth_tile_dialog"
        private const val CONTENT_HEIGHT_PREF_KEY = Prefs.Key.BLUETOOTH_TILE_DIALOG_CONTENT_HEIGHT

        private fun getSubtitleResId(isBluetoothEnabled: Boolean) =
            if (isBluetoothEnabled) R.string.quick_settings_bluetooth_tile_subtitle
            else R.string.bt_is_off
@@ -336,7 +343,10 @@ constructor(

interface BluetoothTileDialogCallback {
    fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)

    fun onSeeAllClicked(view: View)

    fun onPairNewDeviceClicked(view: View)

    fun onAudioSharingButtonClicked(view: View)
}
Loading