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

Commit 4ce92c2d authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge "Move overlay click handling back to SystemUI." into main

parents 84cf1d14 ac00aef0
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