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

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

Merge "Add audio sharing button and highlight audio sharing device." into main

parents b5bdf305 480696a0
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