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

Commit 1d07441d authored by chelseahao's avatar chelseahao
Browse files

Audio sharing dialog on top of bt qs tile dialog.

This CL also created `AudioSharingModule` to provide different implementation based on audio sharing availability.

User could choose to set a device to active or to share audio. Button behavior will be in the next CL.

Test: atest
Bug: 360759048
Flag: com.android.settingslib.flags.audio_sharing_qs_dialog_improvement
Change-Id: I08a2b1aaf4240b71d10f12973815a97027dae4fa
parent c668d7e1
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -425,8 +425,8 @@ filegroup {
        "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
        "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/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt",
        "tests/src/**/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt",
        "tests/src/**/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt",
        "tests/src/**/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt",
        "tests/src/**/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt",
+3 −8
Original line number Diff line number Diff line
@@ -20,8 +20,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.plugins.activityStarter
import com.android.systemui.util.mockito.mock
import org.mockito.kotlin.mock

val Kosmos.bluetoothTileDialogLogger: BluetoothTileDialogLogger by Kosmos.Fixture { mock {} }

@@ -29,14 +28,10 @@ val Kosmos.localBluetoothManager: LocalBluetoothManager by Kosmos.Fixture { mock

val Kosmos.dialogTransitionAnimator: DialogTransitionAnimator by Kosmos.Fixture { mock {} }

val Kosmos.deviceItemActionInteractor: DeviceItemActionInteractor by
val Kosmos.deviceItemActionInteractorImpl: DeviceItemActionInteractorImpl by
    Kosmos.Fixture {
        DeviceItemActionInteractor(
            activityStarter,
            dialogTransitionAnimator,
            localBluetoothManager,
        DeviceItemActionInteractorImpl(
            testDispatcher,
            bluetoothTileDialogLogger,
            uiEventLogger,
        )
    }
+115 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
  ~ 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.
  -->

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root"
    style="@style/Widget.SliceView.Panel"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView android:id="@+id/icon"
        android:layout_width="28dp"
        android:layout_height="28dp"
        android:src="@drawable/ic_bt_le_audio_sharing"
        android:layout_marginTop="5dp"
        android:layout_marginBottom="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toTopOf="@id/title"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:gravity="center_vertical|center_horizontal"
        android:maxLines="1"
        android:ellipsize="end"
        android:text="@string/quick_settings_bluetooth_audio_sharing_dialog_title"
        android:textAppearance="@style/TextAppearance.Dialog.Title"
        android:textSize="24sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toTopOf="@id/subtitle"
        app:layout_constraintTop_toBottomOf="@id/icon" />

    <TextView
        android:id="@+id/subtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:gravity="center_vertical|center_horizontal"
        android:ellipsize="end"
        android:maxLines="2"
        android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
        android:textFontWeight="500"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toTopOf="@id/message"
        app:layout_constraintTop_toBottomOf="@id/title" />

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:gravity="center_vertical|center_horizontal"
        android:ellipsize="end"
        android:maxLines="2"
        android:text="@string/quick_settings_bluetooth_audio_sharing_dialog_message"
        android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toTopOf="@id/share_audio_button"
        app:layout_constraintTop_toBottomOf="@id/subtitle" />

    <Button
        android:id="@+id/share_audio_button"
        style="@style/SettingsLibActionButton"
        android:textColor="?androidprv:attr/textColorOnAccent"
        android:background="@drawable/audio_sharing_rounded_bg_ripple"
        android:layout_marginBottom="4dp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:minHeight="64dp"
        android:contentDescription="@string/accessibility_bluetooth_device_settings_see_all"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/message"
        app:layout_constraintBottom_toTopOf="@+id/switch_active_button"
        android:text="@string/quick_settings_bluetooth_audio_sharing_button"
        android:maxLines="2" />

    <Button
        android:id="@+id/switch_active_button"
        style="@style/SettingsLibActionButton"
        android:textColor="?androidprv:attr/textColorOnAccent"
        android:background="@drawable/audio_sharing_rounded_bg_ripple"
        android:layout_marginBottom="20dp"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:minHeight="64dp"
        android:contentDescription="@string/accessibility_bluetooth_device_settings_pair_new_device"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/share_audio_button"
        app:layout_constraintBottom_toBottomOf="parent"
        android:maxLines="2" />
</androidx.constraintlayout.widget.ConstraintLayout>
 No newline at end of file
+8 −0
Original line number Diff line number Diff line
@@ -765,6 +765,14 @@
    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing audio</string>
    <!-- QuickSettings: Bluetooth dialog audio sharing button text accessibility label. Used as part of the string "Double tap to enter audio sharing settings". [CHAR LIMIT=50]-->
    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility">enter audio sharing settings</string>
    <!-- QuickSettings: Bluetooth audio sharing dialog message. [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_audio_sharing_dialog_message">This device\'s music and videos will play on both pairs of headphones</string>
    <!-- QuickSettings: Bluetooth audio sharing dialog title. [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_audio_sharing_dialog_title">Share your audio</string>
    <!-- QuickSettings: Bluetooth audio sharing dialog subtitle. [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_audio_sharing_dialog_subtitle"><xliff:g id="available_device_name" example="device 1">%1$s</xliff:g> and <xliff:g id="active_device_name" example="device 2">%2$s</xliff:g></string>
    <!-- QuickSettings: Bluetooth audio sharing dialog button text. [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_audio_sharing_dialog_switch_to_button">Switch to <xliff:g id="available_device_name" example="device 1">%1$s</xliff:g></string>

    <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
    <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
+98 −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 androidx.annotation.StringRes
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine

sealed class AudioSharingButtonState {
    object Gone : AudioSharingButtonState()

    data class Visible(@StringRes val resId: Int, val isActive: Boolean) :
        AudioSharingButtonState()
}

class AudioSharingButtonViewModel
@AssistedInject
constructor(
    private val localBluetoothManager: LocalBluetoothManager?,
    private val bluetoothStateInteractor: BluetoothStateInteractor,
    private val deviceItemInteractor: DeviceItemInteractor,
) : ExclusiveActivatable() {

    private val mutableButtonState =
        MutableStateFlow<AudioSharingButtonState>(AudioSharingButtonState.Gone)
    /** Flow representing the update of AudioSharingButtonState. */
    val audioSharingButtonStateUpdate: StateFlow<AudioSharingButtonState> =
        mutableButtonState.asStateFlow()

    override suspend fun onActivated(): Nothing {
        combine(
                bluetoothStateInteractor.bluetoothStateUpdate,
                deviceItemInteractor.deviceItemUpdate
            ) { bluetoothState, deviceItem ->
                getButtonState(bluetoothState, deviceItem)
            }
            .collect { mutableButtonState.value = it }
        awaitCancellation()
    }

    private fun getButtonState(
        bluetoothState: Boolean,
        deviceItem: List<DeviceItem>
    ): AudioSharingButtonState {
        return when {
            // Don't show button when bluetooth is off
            !bluetoothState -> AudioSharingButtonState.Gone
            // Show sharing audio when broadcasting
            BluetoothUtils.isBroadcasting(localBluetoothManager) ->
                AudioSharingButtonState.Visible(
                    R.string.quick_settings_bluetooth_audio_sharing_button_sharing,
                    isActive = true
                )
            // When not broadcasting, don't show button if there's connected source in any device
            deviceItem.any {
                BluetoothUtils.hasConnectedBroadcastSource(
                    it.cachedBluetoothDevice,
                    localBluetoothManager
                )
            } -> AudioSharingButtonState.Gone
            // Show audio sharing when there's a connected LE audio device
            deviceItem.any { BluetoothUtils.isActiveLeAudioDevice(it.cachedBluetoothDevice) } ->
                AudioSharingButtonState.Visible(
                    R.string.quick_settings_bluetooth_audio_sharing_button,
                    isActive = false
                )
            else -> AudioSharingButtonState.Gone
        }
    }

    @AssistedFactory
    interface Factory {
        fun create(): AudioSharingButtonViewModel
    }
}
Loading