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

Commit 480696a0 authored by chelseahao's avatar chelseahao Committed by Chelsea Hao
Browse files

Add audio sharing button and highlight audio sharing device.

Test: atest -c com.android.systemui.bluetooth
Bug: 330807702
Flag: ACONFIG com.android.settingslib.flags.enable_le_audio_sharing DISABLED
Change-Id: I07b6fa7dfdec9467cc971665f2891b9806138e9d
parent 804f0c04
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -255,6 +255,24 @@
                app:barrierDirection="bottom"
                app:constraint_referenced_ids="pair_new_device_button,bluetooth_auto_on_toggle_info_text" />

            <Button
                android:id="@+id/audio_sharing_button"
                style="@style/Widget.Dialog.Button.BorderButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="9dp"
                android:layout_marginBottom="@dimen/dialog_bottom_padding"
                android:layout_marginEnd="@dimen/dialog_side_padding"
                android:layout_marginStart="@dimen/dialog_side_padding"
                android:ellipsize="end"
                android:maxLines="1"
                android:text="@string/quick_settings_bluetooth_audio_sharing_button"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/barrier"
                app:layout_constraintVertical_bias="1"
                android:visibility="gone" />

            <Button
                android:id="@+id/done_button"
                style="@style/Widget.Dialog.Button"
+6 −0
Original line number Diff line number Diff line
@@ -678,6 +678,8 @@
    <string name="turn_on_bluetooth">Use Bluetooth</string>
    <!-- QuickSettings: Bluetooth dialog device connected default summary [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_device_connected">Connected</string>
    <!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
    <string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
    <!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
    <string name="quick_settings_bluetooth_device_saved">Saved</string>
    <!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
@@ -690,6 +692,10 @@
    <string name="turn_on_bluetooth_auto_info_disabled">Features like Quick Share and Find My Device use Bluetooth</string>
    <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
    <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
    <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
    <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
    <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</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>
+93 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn

internal sealed class AudioSharingButtonState {
    object Gone : AudioSharingButtonState()
    data class Visible(@StringRes val resId: Int) : AudioSharingButtonState()
}

/** Holds business logic for the audio sharing state. */
@SysUISingleton
internal class AudioSharingInteractor
@Inject
constructor(
    private val localBluetoothManager: LocalBluetoothManager?,
    bluetoothStateInteractor: BluetoothStateInteractor,
    deviceItemInteractor: DeviceItemInteractor,
    @Application private val coroutineScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {
    /** Flow representing the update of AudioSharingButtonState. */
    internal val audioSharingButtonStateUpdate: Flow<AudioSharingButtonState> =
        combine(
                bluetoothStateInteractor.bluetoothStateUpdate,
                deviceItemInteractor.deviceItemUpdate
            ) { bluetoothState, deviceItem ->
                getButtonState(bluetoothState, deviceItem)
            }
            .flowOn(backgroundDispatcher)
            .stateIn(
                coroutineScope,
                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
                initialValue = AudioSharingButtonState.Gone
            )

    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
                )
            // 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
                )
            else -> AudioSharingButtonState.Gone
        }
    }
}
+19 −6
Original line number Diff line number Diff line
@@ -25,12 +25,17 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
@@ -40,9 +45,10 @@ constructor(
    private val localBluetoothManager: LocalBluetoothManager?,
    private val logger: BluetoothTileDialogLogger,
    @Application private val coroutineScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {

    internal val bluetoothStateUpdate: StateFlow<Boolean?> =
    internal val bluetoothStateUpdate: StateFlow<Boolean> =
        conflatedCallbackFlow {
                val listener =
                    object : BluetoothCallback {
@@ -64,16 +70,22 @@ constructor(
                localBluetoothManager?.eventManager?.registerCallback(listener)
                awaitClose { localBluetoothManager?.eventManager?.unregisterCallback(listener) }
            }
            .onStart { emit(isBluetoothEnabled()) }
            .flowOn(backgroundDispatcher)
            .stateIn(
                coroutineScope,
                SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
                initialValue = null
                initialValue = false
            )

    internal var isBluetoothEnabled: Boolean
        get() = localBluetoothManager?.bluetoothAdapter?.isEnabled == true
        set(value) {
            if (isBluetoothEnabled != value) {
    suspend fun isBluetoothEnabled(): Boolean =
        withContext(backgroundDispatcher) {
            localBluetoothManager?.bluetoothAdapter?.isEnabled == true
        }

    suspend fun setBluetoothEnabled(value: Boolean) {
        withContext(backgroundDispatcher) {
            if (isBluetoothEnabled() != value) {
                localBluetoothManager?.bluetoothAdapter?.apply {
                    if (value) enable() else disable()
                    logger.logBluetoothState(
@@ -83,6 +95,7 @@ constructor(
                }
            }
        }
    }

    companion object {
        private const val TAG = "BtStateInteractor"
+22 −5
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Switch
@@ -59,7 +60,6 @@ class BluetoothTileDialogDelegate
internal constructor(
    @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
    @Assisted private val cachedContentHeight: Int,
    @Assisted private val bluetoothToggleInitialValue: Boolean,
    @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
    @Assisted private val dismissListener: Runnable,
    @Main private val mainDispatcher: CoroutineDispatcher,
@@ -69,8 +69,7 @@ internal constructor(
    private val systemuiDialogFactory: SystemUIDialog.Factory,
) : SystemUIDialog.Delegate {

    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
        MutableStateFlow(bluetoothToggleInitialValue)
    private val mutableBluetoothStateToggle: MutableStateFlow<Boolean?> = MutableStateFlow(null)
    internal val bluetoothStateToggle
        get() = mutableBluetoothStateToggle.asStateFlow()

@@ -99,7 +98,6 @@ internal constructor(
        fun create(
            initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
            cachedContentHeight: Int,
            bluetoothEnabled: Boolean,
            dialogCallback: BluetoothTileDialogCallback,
            dimissListener: Runnable
        ): BluetoothTileDialogDelegate
@@ -130,6 +128,9 @@ internal constructor(
        getPairNewDeviceButton(dialog).setOnClickListener {
            bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
        }
        getAudioSharingButtonView(dialog).setOnClickListener {
            bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
        }
        getScrollViewContent(dialog).apply {
            minimumHeight =
                resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
@@ -211,9 +212,19 @@ internal constructor(
        getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId)
    }

    internal fun onAudioSharingButtonUpdated(
        dialog: SystemUIDialog,
        visibility: Int,
        label: String?
    ) {
        getAudioSharingButtonView(dialog).apply {
            this.visibility = visibility
            label?.let { text = it }
        }
    }

    private fun setupToggle(dialog: SystemUIDialog) {
        val toggleView = getToggleView(dialog)
        toggleView.isChecked = bluetoothToggleInitialValue
        toggleView.setOnCheckedChangeListener { view, isChecked ->
            mutableBluetoothStateToggle.value = isChecked
            view.apply {
@@ -259,6 +270,10 @@ internal constructor(
        return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
    }

    private fun getAudioSharingButtonView(dialog: SystemUIDialog): Button {
        return dialog.requireViewById(R.id.audio_sharing_button)
    }

    private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
        return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
    }
@@ -412,6 +427,8 @@ internal constructor(
        const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
            "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE"
        const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
        const val ACTION_AUDIO_SHARING =
            "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS"
        const val DISABLED_ALPHA = 0.3f
        const val ENABLED_ALPHA = 1f
        const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
Loading