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

Commit ac00aef0 authored by chelseahao's avatar chelseahao
Browse files

Move overlay click handling back to SystemUI.

Test: atest -c com.android.systemui.bluetooth.qsdialog
Bug: 340379827
Flag: NA
Change-Id: Ieddca3ec1f05d30aff94b50e9849b6a80e153666
parent a91340e1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -368,6 +368,7 @@ filegroup {
        "tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
        "tests/src/**/systemui/navigationbar/NavigationBarControllerImplTest.java",
        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt",
        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt",
        "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
        "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
        "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
+26 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.bluetooth.qsdialog

import android.bluetooth.BluetoothDevice
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.DEBUG
import com.android.systemui.log.dagger.BluetoothTileDialogLog
@@ -103,4 +104,29 @@ constructor(@BluetoothTileDialogLog private val logBuffer: LogBuffer) {

    fun logDeviceUiUpdate(duration: Long) =
        logBuffer.log(TAG, DEBUG, { long1 = duration }, { "DeviceUiUpdate. duration=$long1" })

    fun logDeviceClickInAudioSharingWhenEnabled(inAudioSharing: Boolean) {
        logBuffer.log(
            TAG,
            DEBUG,
            { str1 = inAudioSharing.toString() },
            { "DeviceClick. in audio sharing=$str1" }
        )
    }

    fun logConnectedLeByGroupId(map: Map<Int, List<BluetoothDevice>>) {
        logBuffer.log(TAG, DEBUG, { str1 = map.toString() }, { "ConnectedLeByGroupId. map=$str1" })
    }

    fun logLaunchSettingsCriteriaMatched(criteria: String, deviceItem: DeviceItem) {
        logBuffer.log(
            TAG,
            DEBUG,
            {
                str1 = criteria
                str2 = deviceItem.toString()
            },
            { "$str1. deviceItem=$str2" }
        )
    }
}
+0 −29
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 com.android.systemui.dagger.SysUISingleton
import dagger.Binds
import dagger.Module

@Module
interface BluetoothTileDialogModule {
    @Binds
    @SysUISingleton
    fun bindDeviceItemActionInteractor(
        impl: DeviceItemActionInteractorImpl
    ): DeviceItemActionInteractor
}
+11 −1
Original line number Diff line number Diff line
@@ -35,7 +35,17 @@ enum class BluetoothTileDialogUiEvent(val metricId: Int) : UiEventLogger.UiEvent
    CONNECTED_OTHER_DEVICE_DISCONNECT(1508),
    @UiEvent(doc = "The auto on toggle is clicked") BLUETOOTH_AUTO_ON_TOGGLE_CLICKED(1617),
    @UiEvent(doc = "The audio sharing button is clicked")
    BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700);
    BLUETOOTH_AUDIO_SHARING_BUTTON_CLICKED(1700),
    @UiEvent(doc = "Currently broadcasting and a LE audio supported device is clicked")
    LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED(1717),
    @UiEvent(doc = "Currently broadcasting and a non-LE audio supported device is clicked")
    LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED(1718),
    @UiEvent(
        doc = "Not broadcasting, having one connected, another saved LE audio device is clicked"
    )
    LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED(1719),
    @UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
    LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720);

    override fun getId() = metricId
}
+243 −8
Original line number Diff line number Diff line
@@ -16,32 +16,87 @@

package com.android.systemui.bluetooth.qsdialog

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothProfile
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.A2dpProfile
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.HeadsetProfile
import com.android.settingslib.bluetooth.HearingAidProfile
import com.android.settingslib.bluetooth.LeAudioProfile
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor.LaunchSettingsCriteria.Companion.getCurrentConnectedLeByGroupId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

/** Defines interface for click handling of a DeviceItem. */
interface DeviceItemActionInteractor {
    suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog)
}

@SysUISingleton
open class DeviceItemActionInteractorImpl
class DeviceItemActionInteractor
@Inject
constructor(
    private val activityStarter: ActivityStarter,
    private val dialogTransitionAnimator: DialogTransitionAnimator,
    private val localBluetoothManager: LocalBluetoothManager?,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val logger: BluetoothTileDialogLogger,
    private val uiEventLogger: UiEventLogger,
) : DeviceItemActionInteractor {
) {
    private val leAudioProfile: LeAudioProfile?
        get() = localBluetoothManager?.profileManager?.leAudioProfile

    private val assistantProfile: LocalBluetoothLeBroadcastAssistant?
        get() = localBluetoothManager?.profileManager?.leAudioBroadcastAssistantProfile

    override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
    private val launchSettingsCriteriaList: List<LaunchSettingsCriteria>
        get() =
            listOf(
                InSharingClickedNoSource(localBluetoothManager, backgroundDispatcher, logger),
                NotSharingClickedNonConnect(
                    leAudioProfile,
                    assistantProfile,
                    backgroundDispatcher,
                    logger
                ),
                NotSharingClickedConnected(
                    leAudioProfile,
                    assistantProfile,
                    backgroundDispatcher,
                    logger
                )
            )

    suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
        withContext(backgroundDispatcher) {
            logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type)
            if (
                BluetoothUtils.isAudioSharingEnabled() &&
                    localBluetoothManager != null &&
                    leAudioProfile != null &&
                    assistantProfile != null
            ) {
                val inAudioSharing = BluetoothUtils.isBroadcasting(localBluetoothManager)
                logger.logDeviceClickInAudioSharingWhenEnabled(inAudioSharing)

                val criteriaMatched =
                    launchSettingsCriteriaList.firstOrNull {
                        it.matched(inAudioSharing, deviceItem)
                    }
                if (criteriaMatched != null) {
                    uiEventLogger.log(criteriaMatched.getClickUiEvent(deviceItem))
                    launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
                    return@withContext
                }
            }
            deviceItem.cachedBluetoothDevice.apply {
                when (deviceItem.type) {
                    DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> {
@@ -69,4 +124,184 @@ constructor(
            }
        }
    }

    private fun launchSettings(device: BluetoothDevice, dialog: SystemUIDialog) {
        val intent =
            Intent(Settings.ACTION_BLUETOOTH_SETTINGS).apply {
                putExtra(
                    EXTRA_SHOW_FRAGMENT_ARGUMENTS,
                    Bundle().apply {
                        putParcelable(LocalBluetoothLeBroadcast.EXTRA_BLUETOOTH_DEVICE, device)
                    }
                )
            }
        intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
        activityStarter.postStartActivityDismissingKeyguard(
            intent,
            0,
            dialogTransitionAnimator.createActivityTransitionController(dialog)
        )
    }

    private interface LaunchSettingsCriteria {
        suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean

        suspend fun getClickUiEvent(deviceItem: DeviceItem): BluetoothTileDialogUiEvent

        companion object {
            suspend fun getCurrentConnectedLeByGroupId(
                leAudioProfile: LeAudioProfile,
                assistantProfile: LocalBluetoothLeBroadcastAssistant,
                @Background backgroundDispatcher: CoroutineDispatcher,
                logger: BluetoothTileDialogLogger,
            ): Map<Int, List<BluetoothDevice>> {
                return withContext(backgroundDispatcher) {
                    assistantProfile
                        .getDevicesMatchingConnectionStates(
                            intArrayOf(BluetoothProfile.STATE_CONNECTED)
                        )
                        ?.filterNotNull()
                        ?.groupBy { leAudioProfile.getGroupId(it) }
                        ?.also { logger.logConnectedLeByGroupId(it) } ?: emptyMap()
                }
            }
        }
    }

    private class InSharingClickedNoSource(
        private val localBluetoothManager: LocalBluetoothManager?,
        @Background private val backgroundDispatcher: CoroutineDispatcher,
        private val logger: BluetoothTileDialogLogger,
    ) : LaunchSettingsCriteria {
        // If currently broadcasting and the clicked device is not connected to the source
        override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
            return withContext(backgroundDispatcher) {
                val matched =
                    inAudioSharing &&
                        deviceItem.isMediaDevice &&
                        !BluetoothUtils.hasConnectedBroadcastSource(
                            deviceItem.cachedBluetoothDevice,
                            localBluetoothManager
                        )

                if (matched) {
                    logger.logLaunchSettingsCriteriaMatched("InSharingClickedNoSource", deviceItem)
                }

                matched
            }
        }

        override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
            if (deviceItem.isLeAudioSupported)
                BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED
            else BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED
    }

    private class NotSharingClickedNonConnect(
        private val leAudioProfile: LeAudioProfile?,
        private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
        @Background private val backgroundDispatcher: CoroutineDispatcher,
        private val logger: BluetoothTileDialogLogger,
    ) : LaunchSettingsCriteria {
        // If not broadcasting, having one device connected, and clicked on a not yet connected LE
        // audio device
        override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
            return withContext(backgroundDispatcher) {
                val matched =
                    leAudioProfile?.let { leAudio ->
                        assistantProfile?.let { assistant ->
                            !inAudioSharing &&
                                getCurrentConnectedLeByGroupId(
                                        leAudio,
                                        assistant,
                                        backgroundDispatcher,
                                        logger
                                    )
                                    .size == 1 &&
                                deviceItem.isNotConnectedLeAudioSupported
                        }
                    } ?: false

                if (matched) {
                    logger.logLaunchSettingsCriteriaMatched(
                        "NotSharingClickedNonConnect",
                        deviceItem
                    )
                }

                matched
            }
        }

        override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
            BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_SAVED_LE_DEVICE_CLICKED
    }

    private class NotSharingClickedConnected(
        private val leAudioProfile: LeAudioProfile?,
        private val assistantProfile: LocalBluetoothLeBroadcastAssistant?,
        @Background private val backgroundDispatcher: CoroutineDispatcher,
        private val logger: BluetoothTileDialogLogger,
    ) : LaunchSettingsCriteria {
        // If not broadcasting, having two device connected, clicked on any connected LE audio
        // devices
        override suspend fun matched(inAudioSharing: Boolean, deviceItem: DeviceItem): Boolean {
            return withContext(backgroundDispatcher) {
                val matched =
                    leAudioProfile?.let { leAudio ->
                        assistantProfile?.let { assistant ->
                            !inAudioSharing &&
                                getCurrentConnectedLeByGroupId(
                                        leAudio,
                                        assistant,
                                        backgroundDispatcher,
                                        logger
                                    )
                                    .size == 2 &&
                                deviceItem.isActiveOrConnectedLeAudioSupported
                        }
                    } ?: false

                if (matched) {
                    logger.logLaunchSettingsCriteriaMatched(
                        "NotSharingClickedConnected",
                        deviceItem
                    )
                }

                matched
            }
        }

        override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
            BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED
    }

    private companion object {
        const val EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"

        val DeviceItem.isLeAudioSupported: Boolean
            get() =
                cachedBluetoothDevice.profiles.any { profile ->
                    profile is LeAudioProfile && profile.isEnabled(cachedBluetoothDevice.device)
                }

        val DeviceItem.isNotConnectedLeAudioSupported: Boolean
            get() = type == DeviceItemType.SAVED_BLUETOOTH_DEVICE && isLeAudioSupported

        val DeviceItem.isActiveOrConnectedLeAudioSupported: Boolean
            get() =
                (type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE ||
                    type == DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) && isLeAudioSupported

        val DeviceItem.isMediaDevice: Boolean
            get() =
                cachedBluetoothDevice.connectableProfiles.any {
                    it is A2dpProfile ||
                        it is HearingAidProfile ||
                        it is LeAudioProfile ||
                        it is HeadsetProfile
                }
    }
}
Loading