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
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bd7067b..e357001 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -425,8 +425,8 @@
"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",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
index 5ff4634..b5b2f5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt
@@ -20,8 +20,7 @@
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.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,
)
}
diff --git a/packages/SystemUI/res/layout/audio_sharing_dialog.xml b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
new file mode 100644
index 0000000..7534e15
--- /dev/null
+++ b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
@@ -0,0 +1,115 @@
+<?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
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c76b35f..4e9fd4b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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>
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt
new file mode 100644
index 0000000..5213a0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModel.kt
@@ -0,0 +1,98 @@
+/*
+ * 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
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
new file mode 100644
index 0000000..eb5706f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -0,0 +1,148 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice
+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.LocalBluetoothManager
+import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+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
+
+@SysUISingleton
+class AudioSharingDeviceItemActionInteractorImpl
+@Inject
+constructor(
+ private val activityStarter: ActivityStarter,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val logger: BluetoothTileDialogLogger,
+ private val uiEventLogger: UiEventLogger,
+ private val delegateFactory: AudioSharingDialogDelegate.Factory,
+ private val deviceItemActionInteractorImpl: DeviceItemActionInteractorImpl,
+) : DeviceItemActionInteractor {
+
+ override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ withContext(backgroundDispatcher) {
+ val inAudioSharing = BluetoothUtils.isBroadcasting(localBluetoothManager)
+ logger.logDeviceClickInAudioSharingWhenEnabled(inAudioSharing)
+
+ when {
+ deviceItem.type ==
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ if (audioSharingQsDialogImprovement()) {
+ withContext(mainDispatcher) {
+ delegateFactory
+ .create(deviceItem.cachedBluetoothDevice)
+ .createDialog()
+ .let { dialogTransitionAnimator.showFromDialog(it, dialog) }
+ }
+ } else {
+ launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
+ logger.logLaunchSettingsCriteriaMatched(
+ "AvailableAudioSharingDeviceClicked",
+ deviceItem
+ )
+ }
+ uiEventLogger.log(
+ BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
+ )
+ }
+ inSharingAndDeviceNoSource(inAudioSharing, deviceItem) -> {
+ launchSettings(deviceItem.cachedBluetoothDevice.device, dialog)
+ logger.logLaunchSettingsCriteriaMatched("InSharingClickedNoSource", deviceItem)
+ uiEventLogger.log(
+ if (deviceItem.isLeAudioSupported)
+ BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_IN_SHARING_LE_DEVICE_CLICKED
+ else
+ BluetoothTileDialogUiEvent
+ .LAUNCH_SETTINGS_IN_SHARING_NON_LE_DEVICE_CLICKED
+ )
+ }
+ else -> {
+ deviceItemActionInteractorImpl.onClick(deviceItem, dialog)
+ }
+ }
+ }
+ }
+
+ private fun inSharingAndDeviceNoSource(
+ inAudioSharing: Boolean,
+ deviceItem: DeviceItem
+ ): Boolean {
+ return inAudioSharing &&
+ deviceItem.isMediaDevice &&
+ !BluetoothUtils.hasConnectedBroadcastSource(
+ deviceItem.cachedBluetoothDevice,
+ localBluetoothManager
+ )
+ }
+
+ 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 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.isMediaDevice: Boolean
+ get() =
+ cachedBluetoothDevice.uiAccessibleProfiles.any {
+ it is A2dpProfile ||
+ it is HearingAidProfile ||
+ it is LeAudioProfile ||
+ it is HeadsetProfile
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt
new file mode 100644
index 0000000..6b39d8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegate.kt
@@ -0,0 +1,68 @@
+/*
+ * 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 android.os.Bundle
+import android.widget.Button
+import android.widget.TextView
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+class AudioSharingDialogDelegate
+@AssistedInject
+constructor(
+ @Assisted private val cachedBluetoothDevice: CachedBluetoothDevice,
+ @Application private val coroutineScope: CoroutineScope,
+ private val viewModelFactory: AudioSharingDialogViewModel.Factory,
+ private val sysuiDialogFactory: SystemUIDialog.Factory,
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog = sysuiDialogFactory.create(this)
+
+ override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ with(dialog.layoutInflater.inflate(R.layout.audio_sharing_dialog, null)) {
+ dialog.setView(this)
+ val subtitleTextView = requireViewById<TextView>(R.id.subtitle)
+ val switchActiveButton = requireViewById<Button>(R.id.switch_active_button)
+ val job =
+ coroutineScope.launch {
+ viewModelFactory.create(cachedBluetoothDevice).dialogState.collect {
+ when (it) {
+ is AudioSharingDialogState.Hide -> dialog.dismiss()
+ is AudioSharingDialogState.Show -> {
+ subtitleTextView.text = it.subtitle
+ switchActiveButton.text = it.switchButtonText
+ }
+ }
+ }
+ }
+ SystemUIDialog.registerDismissListener(dialog) { job.cancel() }
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(cachedBluetoothDevice: CachedBluetoothDevice): AudioSharingDialogDelegate
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt
new file mode 100644
index 0000000..55686aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModel.kt
@@ -0,0 +1,97 @@
+/*
+ * 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 android.content.Context
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+sealed class AudioSharingDialogState {
+ data object Hide : AudioSharingDialogState()
+
+ data class Show(val subtitle: String, val switchButtonText: String) : AudioSharingDialogState()
+}
+
+class AudioSharingDialogViewModel
+@AssistedInject
+constructor(
+ deviceItemInteractor: DeviceItemInteractor,
+ audioSharingInteractor: AudioSharingInteractor,
+ private val context: Context,
+ private val localBluetoothManager: LocalBluetoothManager?,
+ @Assisted private val cachedBluetoothDevice: CachedBluetoothDevice,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ val dialogState: Flow<AudioSharingDialogState> =
+ deviceItemInteractor.deviceItemUpdateRequest
+ .map {
+ if (
+ audioSharingInteractor.isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice
+ )
+ ) {
+ createShowState(cachedBluetoothDevice)
+ } else {
+ AudioSharingDialogState.Hide
+ }
+ }
+ .onStart { emit(createShowState(cachedBluetoothDevice)) }
+ .flowOn(backgroundDispatcher)
+ .distinctUntilChanged()
+
+ private fun createShowState(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioSharingDialogState {
+ val activeDeviceName =
+ localBluetoothManager
+ ?.profileManager
+ ?.leAudioProfile
+ ?.activeDevices
+ ?.firstOrNull()
+ ?.let { localBluetoothManager.cachedDeviceManager?.findDevice(it)?.name } ?: ""
+ val availableDeviceName = cachedBluetoothDevice.name
+ return AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ availableDeviceName,
+ activeDeviceName
+ ),
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ availableDeviceName
+ )
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice,
+ ): AudioSharingDialogViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 817f2d7..227ccfe 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -16,82 +16,45 @@
package com.android.systemui.bluetooth.qsdialog
-import androidx.annotation.StringRes
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
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, val isActive: Boolean) :
- AudioSharingButtonState()
-}
+import kotlinx.coroutines.withContext
/** Holds business logic for the audio sharing state. */
+interface AudioSharingInteractor {
+ suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): Boolean
+}
+
@SysUISingleton
-internal class AudioSharingInteractor
+class AudioSharingInteractorImpl
@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
- )
+) : AudioSharingInteractor {
- 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
+ override suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): Boolean {
+ return withContext(backgroundDispatcher) {
+ BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice,
+ localBluetoothManager
+ )
}
}
}
+
+@SysUISingleton
+class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
+ override suspend fun isAvailableAudioSharingMediaBluetoothDevice(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ) = false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
index 17f9e63..55d4d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractor.kt
@@ -39,7 +39,7 @@
/** Holds business logic for the Bluetooth Dialog's bluetooth and device connection state */
@SysUISingleton
-internal class BluetoothStateInteractor
+class BluetoothStateInteractor
@Inject
constructor(
private val localBluetoothManager: LocalBluetoothManager?,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
index bdd4c16..12a6626 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogUiEvent.kt
@@ -42,6 +42,7 @@
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),
+ @Deprecated("Use case no longer needed")
@UiEvent(
doc = "Not broadcasting, having one connected, another saved LE audio device is clicked"
)
@@ -52,6 +53,7 @@
)
@UiEvent(doc = "Not broadcasting, one of the two connected LE audio devices is clicked")
LAUNCH_SETTINGS_NOT_SHARING_CONNECTED_LE_DEVICE_CLICKED(1720),
+ @Deprecated("Use case no longer needed")
@UiEvent(doc = "Not broadcasting, having two connected, the active LE audio devices is clicked")
LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_LE_DEVICE_CLICKED(1881);
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index a8f7fc3..a1b6e0b 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -67,11 +67,12 @@
private val deviceItemActionInteractor: DeviceItemActionInteractor,
private val bluetoothStateInteractor: BluetoothStateInteractor,
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
- private val audioSharingInteractor: AudioSharingInteractor,
+ private val audioSharingButtonViewModelFactory: AudioSharingButtonViewModel.Factory,
private val bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
private val uiEventLogger: UiEventLogger,
+ private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@@ -154,28 +155,31 @@
.launchIn(this)
if (BluetoothUtils.isAudioSharingEnabled()) {
- audioSharingInteractor.audioSharingButtonStateUpdate
- .onEach {
- when (it) {
- is AudioSharingButtonState.Visible -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- VISIBLE,
- context.getString(it.resId),
- it.isActive
- )
- }
- is AudioSharingButtonState.Gone -> {
- dialogDelegate.onAudioSharingButtonUpdated(
- dialog,
- GONE,
- label = null,
- isActive = false
- )
+ audioSharingButtonViewModelFactory.create().run {
+ audioSharingButtonStateUpdate
+ .onEach {
+ when (it) {
+ is AudioSharingButtonState.Visible -> {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ VISIBLE,
+ context.getString(it.resId),
+ it.isActive
+ )
+ }
+ is AudioSharingButtonState.Gone -> {
+ dialogDelegate.onAudioSharingButtonUpdated(
+ dialog,
+ GONE,
+ label = null,
+ isActive = false
+ )
+ }
}
}
- }
- .launchIn(this)
+ .launchIn(this@launch)
+ launch { activate() }
+ }
}
// bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
@@ -209,7 +213,10 @@
// deviceItemClick is emitted when user clicked on a device item.
dialogDelegate.deviceItemClick
- .onEach { deviceItemActionInteractor.onClick(it, dialog) }
+ .onEach {
+ deviceItemActionInteractor.onClick(it, dialog)
+ logger.logDeviceClick(it.cachedBluetoothDevice.address, it.type)
+ }
.launchIn(this)
// contentHeight is emitted when the dialog is dismissed.
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
index f1894d3..cf0f19f 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt
@@ -16,87 +16,28 @@
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
+interface DeviceItemActionInteractor {
+ suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {}
+}
+
@SysUISingleton
-class DeviceItemActionInteractor
+class DeviceItemActionInteractorImpl
@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,
-) {
- private val leAudioProfile: LeAudioProfile?
- get() = localBluetoothManager?.profileManager?.leAudioProfile
+) : DeviceItemActionInteractor {
- private val assistantProfile: LocalBluetoothLeBroadcastAssistant?
- get() = localBluetoothManager?.profileManager?.leAudioBroadcastAssistantProfile
-
- private val launchSettingsCriteriaList: List<LaunchSettingsCriteria>
- get() =
- listOf(
- InSharingClickedNoSource(localBluetoothManager, backgroundDispatcher, logger),
- NotSharingClickedNonConnect(
- leAudioProfile,
- assistantProfile,
- backgroundDispatcher,
- logger
- ),
- NotSharingClickedActive(
- leAudioProfile,
- assistantProfile,
- backgroundDispatcher,
- logger
- )
- )
-
- suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) {
+ override 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 -> {
@@ -106,12 +47,6 @@
DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED)
}
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
- // TODO(b/360759048): pop up dialog
- uiEventLogger.log(
- BluetoothTileDialogUiEvent.AVAILABLE_AUDIO_SHARING_DEVICE_CLICKED
- )
- }
DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
setActive()
uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE)
@@ -126,186 +61,12 @@
connect()
uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT)
}
- }
- }
- }
- }
-
- 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)
+ DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
+ // Do nothing. Should already be handled in
+ // AudioSharingDeviceItemActionInteractor.
}
- )
- }
- 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 NotSharingClickedActive(
- 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 the active 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 == 2 &&
- deviceItem.isActiveLeAudioSupported
- }
- } ?: false
-
- if (matched) {
- logger.logLaunchSettingsCriteriaMatched(
- "NotSharingClickedConnected",
- deviceItem
- )
- }
-
- matched
- }
- }
-
- override suspend fun getClickUiEvent(deviceItem: DeviceItem) =
- BluetoothTileDialogUiEvent.LAUNCH_SETTINGS_NOT_SHARING_ACTIVE_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.isActiveLeAudioSupported: Boolean
- get() = type == DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE && isLeAudioSupported
-
- val DeviceItem.isMediaDevice: Boolean
- get() =
- cachedBluetoothDevice.uiAccessibleProfiles.any {
- it is A2dpProfile ||
- it is HearingAidProfile ||
- it is LeAudioProfile ||
- it is HeadsetProfile
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index 7280489..d55d155 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.flags.Flags
-import com.android.settingslib.flags.Flags.enableLeAudioSharing
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -107,8 +106,7 @@
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager
): Boolean {
- return enableLeAudioSharing() &&
- BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
+ return BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -133,8 +131,7 @@
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager
): Boolean {
- return BluetoothUtils.isAudioSharingEnabled() &&
- super.isFilterMatched(context, cachedDevice, audioManager) &&
+ return super.isFilterMatched(context, cachedDevice, audioManager) &&
BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
cachedDevice,
localBluetoothManager
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 9114eca..0118e56 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -54,6 +54,8 @@
private val localBluetoothManager: LocalBluetoothManager?,
private val systemClock: SystemClock,
private val logger: BluetoothTileDialogLogger,
+ private val deviceItemFactoryList: List<@JvmSuppressWildcards DeviceItemFactory>,
+ private val deviceItemDisplayPriority: List<@JvmSuppressWildcards DeviceItemType>,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
@@ -67,7 +69,7 @@
internal val showSeeAllUpdate
get() = mutableShowSeeAllUpdate.asStateFlow()
- internal val deviceItemUpdateRequest: SharedFlow<Unit> =
+ val deviceItemUpdateRequest: SharedFlow<Unit> =
conflatedCallbackFlow {
val listener =
object : BluetoothCallback {
@@ -114,26 +116,6 @@
}
.shareIn(coroutineScope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0))
- private var deviceItemFactoryList: List<DeviceItemFactory> =
- listOf(
- ActiveMediaDeviceItemFactory(),
- AudioSharingMediaDeviceItemFactory(localBluetoothManager),
- AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager),
- AvailableMediaDeviceItemFactory(),
- ConnectedDeviceItemFactory(),
- SavedDeviceItemFactory()
- )
-
- private var displayPriority: List<DeviceItemType> =
- listOf(
- DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- )
-
internal suspend fun updateDeviceItems(context: Context, trigger: DeviceFetchTrigger) {
withContext(backgroundDispatcher) {
val start = systemClock.elapsedRealtime()
@@ -144,7 +126,7 @@
.firstOrNull { it.isFilterMatched(context, cachedDevice, audioManager) }
?.create(context, cachedDevice)
}
- .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
+ .sort(deviceItemDisplayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
// Only emit when the job is not cancelled
if (isActive) {
mutableDeviceItemUpdate.tryEmit(deviceItems.take(MAX_DEVICE_ITEM_ENTRY))
@@ -176,14 +158,6 @@
)
}
- internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) {
- deviceItemFactoryList = list
- }
-
- internal fun setDisplayPriorityForTesting(list: List<DeviceItemType>) {
- displayPriority = list
- }
-
companion object {
private const val TAG = "DeviceItemInteractor"
private const val MAX_DEVICE_ITEM_ENTRY = 3
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
new file mode 100644
index 0000000..3b1cf07
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.dagger
+
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.bluetooth.qsdialog.ActiveMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AudioSharingDeviceItemActionInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractor
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractorEmptyImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.AudioSharingMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AvailableAudioSharingMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.AvailableMediaDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.ConnectedDeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractor
+import com.android.systemui.bluetooth.qsdialog.DeviceItemActionInteractorImpl
+import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory
+import com.android.systemui.bluetooth.qsdialog.DeviceItemType
+import com.android.systemui.bluetooth.qsdialog.SavedDeviceItemFactory
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+
+/** Dagger module for audio sharing code for BT QS dialog */
+@Module
+interface AudioSharingModule {
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ fun provideAudioSharingInteractor(
+ impl: Lazy<AudioSharingInteractorImpl>,
+ emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
+ ): AudioSharingInteractor =
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ impl.get()
+ } else {
+ emptyImpl.get()
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemActionInteractor(
+ audioSharingImpl: Lazy<AudioSharingDeviceItemActionInteractorImpl>,
+ impl: Lazy<DeviceItemActionInteractorImpl>,
+ ): DeviceItemActionInteractor =
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ audioSharingImpl.get()
+ } else {
+ impl.get()
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemFactoryList(
+ localBluetoothManager: LocalBluetoothManager?
+ ): List<DeviceItemFactory> = buildList {
+ add(ActiveMediaDeviceItemFactory())
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ add(AudioSharingMediaDeviceItemFactory(localBluetoothManager))
+ add(AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager))
+ }
+ add(AvailableMediaDeviceItemFactory())
+ add(ConnectedDeviceItemFactory())
+ add(SavedDeviceItemFactory())
+ }
+
+ @Provides
+ @SysUISingleton
+ fun provideDeviceItemDisplayPriority(): List<DeviceItemType> = buildList {
+ add(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
+ if (BluetoothUtils.isAudioSharingEnabled()) {
+ add(DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+ add(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
+ }
+ add(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE)
+ add(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
+ add(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index dac0102..10090283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.connectivity
import android.os.UserManager
+import com.android.systemui.bluetooth.qsdialog.dagger.AudioSharingModule
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION
import com.android.systemui.qs.QsEventLogger
@@ -56,7 +57,7 @@
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
-@Module
+@Module(includes = [AudioSharingModule::class])
interface ConnectivityModule {
/** Inject BluetoothTile into tileMap in QSModule */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt
new file mode 100644
index 0000000..df10f03
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.kosmos.Kosmos
+
+val Kosmos.audioSharingButtonViewModel: AudioSharingButtonViewModel by
+ Kosmos.Fixture {
+ AudioSharingButtonViewModel(
+ localBluetoothManager,
+ bluetoothStateInteractor,
+ deviceItemInteractor,
+ )
+ }
+
+val Kosmos.audioSharingButtonViewModelFactory: AudioSharingButtonViewModel.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingButtonViewModel.Factory {
+ override fun create(): AudioSharingButtonViewModel {
+ return audioSharingButtonViewModel
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
index 2c53fd6..fa9c27e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -46,7 +47,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class AudioSharingInteractorTest : SysuiTestCase() {
+class AudioSharingButtonViewModelTest : SysuiTestCase() {
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val bluetoothState = MutableStateFlow(false)
@@ -57,7 +58,7 @@
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@Mock private lateinit var deviceItem: DeviceItem
private lateinit var mockitoSession: StaticMockitoSession
- private lateinit var audioSharingInteractor: AudioSharingInteractor
+ private lateinit var audioSharingButtonViewModel: AudioSharingButtonViewModel
@Before
fun setUp() {
@@ -65,14 +66,13 @@
mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
whenever(bluetoothStateInteractor.bluetoothStateUpdate).thenReturn(bluetoothState)
whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
- audioSharingInteractor =
- AudioSharingInteractor(
+ audioSharingButtonViewModel =
+ AudioSharingButtonViewModel(
localBluetoothManager,
bluetoothStateInteractor,
deviceItemInteractor,
- testScope.backgroundScope,
- testDispatcher,
)
+ audioSharingButtonViewModel.activateIn(testScope)
}
@After
@@ -83,7 +83,8 @@
@Test
fun testButtonStateUpdate_bluetoothOff_returnGone() {
testScope.runTest {
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
assertThat(actual).isEqualTo(AudioSharingButtonState.Gone)
}
@@ -92,7 +93,8 @@
@Test
fun testButtonStateUpdate_noDevice_returnGone() {
testScope.runTest {
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
bluetoothState.value = true
runCurrent()
@@ -105,7 +107,8 @@
testScope.runTest {
whenever(BluetoothUtils.isBroadcasting(localBluetoothManager)).thenReturn(true)
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
bluetoothState.value = true
deviceItemUpdate.emit(listOf())
runCurrent()
@@ -133,7 +136,8 @@
)
.thenReturn(true)
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
bluetoothState.value = true
deviceItemUpdate.emit(listOf(deviceItem))
runCurrent()
@@ -156,7 +160,8 @@
.thenReturn(false)
whenever(BluetoothUtils.isActiveLeAudioDevice(cachedBluetoothDevice)).thenReturn(true)
- val actual by collectLastValue(audioSharingInteractor.audioSharingButtonStateUpdate)
+ val actual by
+ collectLastValue(audioSharingButtonViewModel.audioSharingButtonStateUpdate)
bluetoothState.value = true
deviceItemUpdate.emit(listOf(deviceItem))
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt
new file mode 100644
index 0000000..ea167df
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorKosmos.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.internal.logging.uiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.audioSharingDeviceItemActionInteractorImpl: AudioSharingDeviceItemActionInteractorImpl by
+ Kosmos.Fixture {
+ AudioSharingDeviceItemActionInteractorImpl(
+ activityStarter,
+ dialogTransitionAnimator,
+ localBluetoothManager,
+ testDispatcher,
+ testDispatcher,
+ bluetoothTileDialogLogger,
+ uiEventLogger,
+ audioSharingDialogDelegateFactory,
+ deviceItemActionInteractorImpl,
+ )
+ }
+
+val Kosmos.audioSharingDialogDelegateFactory: AudioSharingDialogDelegate.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingDialogDelegate.Factory {
+ override fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioSharingDialogDelegate {
+ return audioSharingDialogDelegate
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
new file mode 100644
index 0000000..130f774
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -0,0 +1,189 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.flags.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+ private lateinit var actionInteractorImpl: DeviceItemActionInteractor
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
+ private lateinit var connectedMediaDeviceItem: DeviceItem
+ @Mock private lateinit var dialog: SystemUIDialog
+ @Mock private lateinit var leAudioProfile: LeAudioProfile
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+
+ @Before
+ fun setUp() {
+ mockitoSession =
+ mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
+ connectedMediaDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null
+ )
+ connectedAudioSharingMediaDeviceItem =
+ DeviceItem(
+ type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
+ deviceName = DEVICE_NAME,
+ connectionSummary = DEVICE_CONNECTION_SUMMARY,
+ iconWithDescription = null,
+ background = null
+ )
+ actionInteractorImpl = kosmos.audioSharingDeviceItemActionInteractorImpl
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+ fun testOnClick_connectedAudioSharingMediaDevice_flagOn_createDialog() {
+ with(kosmos) {
+ testScope.runTest {
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(dialogTransitionAnimator)
+ .showFromDialog(any(), any(), eq(null), anyBoolean())
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+ fun testOnClick_connectedAudioSharingMediaDevice_flagOff_shouldLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ verify(dialogTransitionAnimator, never())
+ .showFromDialog(any(), any(), eq(null), anyBoolean())
+ }
+ }
+ }
+
+ @Test
+ fun testOnClick_inAudioSharing_clickedDeviceHasSource_shouldNotLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
+ whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
+ )
+ .thenReturn(true)
+
+ actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+ verify(activityStarter, Mockito.never())
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ }
+ }
+ }
+
+ @Test
+ fun testOnClick_inAudioSharing_clickedDeviceNoSource_shouldLaunchSettings() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
+ whenever(cachedBluetoothDevice.uiAccessibleProfiles)
+ .thenReturn(listOf(leAudioProfile))
+
+ whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
+ whenever(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.any()
+ )
+ )
+ .thenReturn(false)
+
+ actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
+ verify(activityStarter)
+ .postStartActivityDismissingKeyguard(
+ ArgumentMatchers.any(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ }
+ }
+ }
+
+ private companion object {
+ const val DEVICE_NAME = "device"
+ const val DEVICE_CONNECTION_SUMMARY = "active"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
new file mode 100644
index 0000000..1e0a7db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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 android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.Button
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDialogDelegateTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
+ private val updateFlow = MutableSharedFlow<Unit>()
+ private lateinit var underTest: AudioSharingDialogDelegate
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ // TODO(b/364515243): use real object instead of mock
+ whenever(deviceItemInteractor.deviceItemUpdateRequest).thenReturn(updateFlow)
+ whenever(deviceItemInteractor.deviceItemUpdate)
+ .thenReturn(MutableStateFlow(emptyList()))
+ underTest = audioSharingDialogDelegate
+ }
+ }
+
+ @Test
+ fun testCreateDialog() =
+ kosmos.testScope.runTest {
+ val dialog = underTest.createDialog()
+ assertThat(dialog).isInstanceOf(SystemUIDialog::class.java)
+ }
+
+ @Test
+ fun testCreateDialog_showState() =
+ with(kosmos) {
+ testScope.runTest {
+ val availableDeviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(availableDeviceName)
+ val dialog = underTest.createDialog()
+ dialog.show()
+ runCurrent()
+ val subtitleTextView = dialog.findViewById<TextView>(R.id.subtitle)
+ val switchActiveButton = dialog.findViewById<Button>(R.id.switch_active_button)
+ val subtitle =
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ availableDeviceName,
+ ""
+ )
+ val switchButtonText =
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ availableDeviceName
+ )
+ assertThat(subtitleTextView.text).isEqualTo(subtitle)
+ assertThat(switchActiveButton.text).isEqualTo(switchButtonText)
+ dialog.dismiss()
+ }
+ }
+
+ @Test
+ fun testCreateDialog_hideState() =
+ with(kosmos) {
+ testScope.runTest {
+ val dialog = spy(underTest.createDialog())
+ dialog.show()
+ runCurrent()
+ updateFlow.emit(Unit)
+ runCurrent()
+ verify(dialog).dismiss()
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt
new file mode 100644
index 0000000..3ffd771
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelKosmos.kt
@@ -0,0 +1,60 @@
+/*
+ * 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 android.content.applicationContext
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.phone.systemUIDialogDotFactory
+import org.mockito.kotlin.mock
+
+val Kosmos.cachedBluetoothDevice: CachedBluetoothDevice by Kosmos.Fixture { mock {} }
+
+val Kosmos.audioSharingDialogViewModel: AudioSharingDialogViewModel by
+ Kosmos.Fixture {
+ AudioSharingDialogViewModel(
+ deviceItemInteractor,
+ audioSharingInteractor,
+ applicationContext,
+ localBluetoothManager,
+ cachedBluetoothDevice,
+ testDispatcher
+ )
+ }
+
+val Kosmos.audioSharingDialogViewModelFactory: AudioSharingDialogViewModel.Factory by
+ Kosmos.Fixture {
+ object : AudioSharingDialogViewModel.Factory {
+ override fun create(
+ cachedBluetoothDevice: CachedBluetoothDevice
+ ): AudioSharingDialogViewModel {
+ return audioSharingDialogViewModel
+ }
+ }
+ }
+
+val Kosmos.audioSharingDialogDelegate: AudioSharingDialogDelegate by
+ Kosmos.Fixture {
+ AudioSharingDialogDelegate(
+ cachedBluetoothDevice,
+ testScope.backgroundScope,
+ audioSharingDialogViewModelFactory,
+ systemUIDialogDotFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
new file mode 100644
index 0000000..beb816c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogViewModelTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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 android.bluetooth.BluetoothDevice
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.LeAudioProfile
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.cachedBluetoothDeviceManager
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingDialogViewModelTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+ @Mock private lateinit var profileManager: LocalBluetoothProfileManager
+ @Mock private lateinit var leAudioProfile: LeAudioProfile
+ private val updateFlow = MutableSharedFlow<Unit>()
+ private lateinit var underTest: AudioSharingDialogViewModel
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ // TODO(b/364515243): use real object instead of mock
+ whenever(deviceItemInteractor.deviceItemUpdateRequest).thenReturn(updateFlow)
+ whenever(deviceItemInteractor.deviceItemUpdate)
+ .thenReturn(MutableStateFlow(emptyList()))
+ underTest = audioSharingDialogViewModel
+ }
+ }
+
+ @Test
+ fun testDialogState_show() =
+ with(kosmos) {
+ testScope.runTest {
+ val deviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(deviceName)
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ deviceName,
+ ""
+ ),
+ context.getString(
+ R.string
+ .quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ deviceName
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDialogState_showWithActiveDeviceName() =
+ with(kosmos) {
+ testScope.runTest {
+ val deviceName = "name"
+ whenever(cachedBluetoothDevice.name).thenReturn(deviceName)
+ whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
+ whenever(localBluetoothManager.cachedDeviceManager)
+ .thenReturn(cachedBluetoothDeviceManager)
+ whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
+ whenever(leAudioProfile.activeDevices).thenReturn(listOf(mock<BluetoothDevice>()))
+ whenever(cachedBluetoothDeviceManager.findDevice(any()))
+ .thenReturn(cachedBluetoothDevice)
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ assertThat(actual)
+ .isEqualTo(
+ AudioSharingDialogState.Show(
+ context.getString(
+ R.string.quick_settings_bluetooth_audio_sharing_dialog_subtitle,
+ deviceName,
+ deviceName
+ ),
+ context.getString(
+ R.string
+ .quick_settings_bluetooth_audio_sharing_dialog_switch_to_button,
+ deviceName
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun testDialogState_hide() =
+ with(kosmos) {
+ testScope.runTest {
+ val actual by collectLastValue(underTest.dialogState)
+ runCurrent()
+ updateFlow.emit(Unit)
+ assertThat(actual).isEqualTo(AudioSharingDialogState.Hide)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
new file mode 100644
index 0000000..8e473be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+
+val Kosmos.audioSharingInteractor: AudioSharingInteractor by
+ Kosmos.Fixture {
+ AudioSharingInteractorImpl(
+ localBluetoothManager,
+ testDispatcher,
+ )
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt
new file mode 100644
index 0000000..aaa918c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothStateInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.bluetoothStateInteractor: BluetoothStateInteractor by
+ Kosmos.Fixture {
+ BluetoothStateInteractor(
+ localBluetoothManager,
+ bluetoothTileDialogLogger,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index d7bea66..bd7a60c45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -33,6 +33,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.getMutableStateFlow
@@ -68,6 +69,7 @@
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+ private val kosmos = testKosmos()
private val fakeSystemClock = FakeSystemClock()
private val backgroundExecutor = FakeExecutor(fakeSystemClock)
@@ -75,8 +77,6 @@
@Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
- @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
-
@Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
@Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
@@ -120,6 +120,8 @@
scheduler = TestCoroutineScheduler()
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
+ // TODO(b/364515243): use real object instead of mock
+ whenever(kosmos.deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
bluetoothTileDialogViewModel =
BluetoothTileDialogViewModel(
deviceItemInteractor,
@@ -139,11 +141,12 @@
dispatcher
)
),
- audioSharingInteractor,
+ kosmos.audioSharingButtonViewModelFactory,
bluetoothDeviceMetadataInteractor,
mDialogTransitionAnimator,
activityStarter,
uiEventLogger,
+ bluetoothTileDialogLogger,
testScope.backgroundScope,
dispatcher,
dispatcher,
@@ -161,13 +164,10 @@
whenever(sysuiDialog.context).thenReturn(mContext)
whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
.thenReturn(getMutableStateFlow(false))
- whenever(bluetoothTileDialogDelegate.deviceItemClick)
- .thenReturn(getMutableStateFlow(deviceItem))
+ whenever(bluetoothTileDialogDelegate.deviceItemClick).thenReturn(MutableSharedFlow())
whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
.thenReturn(getMutableStateFlow(false))
- whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
- .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 681ea75..9c427c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,34 +15,22 @@
*/
package com.android.systemui.bluetooth.qsdialog
-import android.bluetooth.BluetoothDevice
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
-import com.android.dx.mockito.inline.extended.StaticMockitoSession
-import com.android.settingslib.bluetooth.BluetoothUtils
-import com.android.settingslib.bluetooth.CachedBluetoothDevice
-import com.android.settingslib.bluetooth.LeAudioProfile
-import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
-import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -56,28 +44,18 @@
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
private lateinit var actionInteractorImpl: DeviceItemActionInteractor
- private lateinit var mockitoSession: StaticMockitoSession
private lateinit var activeMediaDeviceItem: DeviceItem
private lateinit var notConnectedDeviceItem: DeviceItem
- private lateinit var connectedAudioSharingMediaDeviceItem: DeviceItem
private lateinit var connectedMediaDeviceItem: DeviceItem
private lateinit var connectedOtherDeviceItem: DeviceItem
@Mock private lateinit var dialog: SystemUIDialog
- @Mock private lateinit var profileManager: LocalBluetoothProfileManager
- @Mock private lateinit var leAudioProfile: LeAudioProfile
- @Mock private lateinit var assistantProfile: LocalBluetoothLeBroadcastAssistant
- @Mock private lateinit var bluetoothDevice: BluetoothDevice
- @Mock private lateinit var bluetoothDeviceGroupId2: BluetoothDevice
- @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
@Before
fun setUp() {
- mockitoSession =
- mockitoSession().initMocks(this).mockStatic(BluetoothUtils::class.java).startMocking()
activeMediaDeviceItem =
DeviceItem(
type = DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -86,7 +64,7 @@
notConnectedDeviceItem =
DeviceItem(
type = DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -95,16 +73,7 @@
connectedMediaDeviceItem =
DeviceItem(
type = DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
- deviceName = DEVICE_NAME,
- connectionSummary = DEVICE_CONNECTION_SUMMARY,
- iconWithDescription = null,
- background = null
- )
- connectedAudioSharingMediaDeviceItem =
- DeviceItem(
- type = DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
@@ -113,18 +82,13 @@
connectedOtherDeviceItem =
DeviceItem(
type = DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
- cachedBluetoothDevice = cachedBluetoothDevice,
+ cachedBluetoothDevice = kosmos.cachedBluetoothDevice,
deviceName = DEVICE_NAME,
connectionSummary = DEVICE_CONNECTION_SUMMARY,
iconWithDescription = null,
background = null
)
- actionInteractorImpl = kosmos.deviceItemActionInteractor
- }
-
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
+ actionInteractorImpl = kosmos.deviceItemActionInteractorImpl
}
@Test
@@ -132,14 +96,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
verify(cachedBluetoothDevice).setActive()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE
- )
}
}
}
@@ -149,14 +107,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
verify(cachedBluetoothDevice).disconnect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE
- )
}
}
}
@@ -166,14 +118,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(connectedOtherDeviceItem, dialog)
verify(cachedBluetoothDevice).disconnect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
- )
}
}
}
@@ -183,293 +129,8 @@
with(kosmos) {
testScope.runTest {
whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
verify(cachedBluetoothDevice).connect()
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.SAVED_BLUETOOTH_DEVICE
- )
- }
- }
- }
-
- @Test
- fun testOnClick_connectedAudioSharingMediaDevice_logClick() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
- verify(bluetoothTileDialogLogger)
- .logDeviceClick(
- cachedBluetoothDevice.address,
- DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE
- )
- }
- }
- }
-
- @Test
- fun testOnClick_audioSharingDisabled_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_inAudioSharing_clickedDeviceHasSource_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.uiAccessibleProfiles)
- .thenReturn(listOf(leAudioProfile))
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- )
- .thenReturn(true)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_inAudioSharing_clickedDeviceNoSource_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.uiAccessibleProfiles)
- .thenReturn(listOf(leAudioProfile))
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(BluetoothUtils.isBroadcasting(ArgumentMatchers.any())).thenReturn(true)
- whenever(
- BluetoothUtils.hasConnectedBroadcastSource(
- ArgumentMatchers.any(),
- ArgumentMatchers.any()
- )
- )
- .thenReturn(false)
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_noConnectedLeDevice_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedNonLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedLe_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.profiles).thenReturn(listOf(leAudioProfile))
- whenever(leAudioProfile.isEnabled(ArgumentMatchers.any())).thenReturn(true)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasOneConnectedLeDevice_clickedConnectedLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice))
-
- actionInteractorImpl.onClick(connectedMediaDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasTwoConnectedLeDevice_clickedNotConnectedLe_shouldNotLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice, bluetoothDeviceGroupId2))
- whenever(leAudioProfile.getGroupId(ArgumentMatchers.any())).thenAnswer {
- val device = it.arguments.first() as BluetoothDevice
- if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
- }
-
- actionInteractorImpl.onClick(notConnectedDeviceItem, dialog)
- verify(activityStarter, Mockito.never())
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
- }
- }
- }
-
- @Test
- fun testOnClick_hasTwoConnectedLeDevice_clickedActiveLe_shouldLaunchSettings() {
- with(kosmos) {
- testScope.runTest {
- whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
- whenever(cachedBluetoothDevice.address).thenReturn(DEVICE_ADDRESS)
- whenever(cachedBluetoothDevice.profiles).thenReturn(listOf(leAudioProfile))
- whenever(leAudioProfile.isEnabled(ArgumentMatchers.any())).thenReturn(true)
-
- whenever(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
- whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
- whenever(profileManager.leAudioProfile).thenReturn(leAudioProfile)
- whenever(profileManager.leAudioBroadcastAssistantProfile)
- .thenReturn(assistantProfile)
-
- whenever(
- assistantProfile.getDevicesMatchingConnectionStates(ArgumentMatchers.any())
- )
- .thenReturn(listOf(bluetoothDevice, bluetoothDeviceGroupId2))
- whenever(leAudioProfile.getGroupId(ArgumentMatchers.any())).thenAnswer {
- val device = it.arguments.first() as BluetoothDevice
- if (device == bluetoothDevice) GROUP_ID_1 else GROUP_ID_2
- }
-
- actionInteractorImpl.onClick(activeMediaDeviceItem, dialog)
- verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- ArgumentMatchers.any(),
- ArgumentMatchers.anyInt(),
- ArgumentMatchers.any()
- )
}
}
}
@@ -478,7 +139,5 @@
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
const val DEVICE_ADDRESS = "address"
- const val GROUP_ID_1 = 1
- const val GROUP_ID_2 = 2
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
index ef441c1..274f916 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt
@@ -133,10 +133,6 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_flagOff_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING off or the device doesn't support broadcast
- // source or assistant.
- `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(false)
-
assertThat(
AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager)
.isFilterMatched(context, cachedDevice, audioManager)
@@ -146,9 +142,6 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isActiveDevice_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
- `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(true)
assertThat(
@@ -160,9 +153,6 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_isNotAvailable_returnsFalse() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
- `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
`when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
`when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
@@ -177,9 +167,6 @@
@Test
fun testAvailableAudioSharingMediaDeviceItemFactory_isFilterMatched_returnsTrue() {
- // Flags.FLAG_ENABLE_LE_AUDIO_SHARING on and the device support broadcast source and
- // assistant.
- `when`(BluetoothUtils.isAudioSharingEnabled()).thenReturn(true)
`when`(BluetoothUtils.isActiveMediaDevice(any())).thenReturn(false)
`when`(BluetoothUtils.isAvailableMediaBluetoothDevice(any(), any())).thenReturn(true)
`when`(BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index 194590c..c39b9a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -83,18 +83,6 @@
fun setUp() {
dispatcher = UnconfinedTestDispatcher()
testScope = TestScope(dispatcher)
- interactor =
- DeviceItemInteractor(
- bluetoothTileDialogRepository,
- audioManager,
- adapter,
- localBluetoothManager,
- fakeSystemClock,
- logger,
- testScope.backgroundScope,
- dispatcher
- )
-
`when`(deviceItem1.cachedBluetoothDevice).thenReturn(cachedDevice1)
`when`(deviceItem2.cachedBluetoothDevice).thenReturn(cachedDevice2)
`when`(cachedDevice1.address).thenReturn("ADDRESS")
@@ -108,9 +96,19 @@
fun testUpdateDeviceItems_noCachedDevice_returnEmpty() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(emptyList())
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -125,9 +123,19 @@
fun testUpdateDeviceItems_hasCachedDevice_filterNotMatch_returnEmpty() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ false }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ false }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -142,9 +150,19 @@
fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnDeviceItem() {
testScope.runTest {
`when`(bluetoothTileDialogRepository.cachedDevices).thenReturn(listOf(cachedDevice1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem1))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem1)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -159,9 +177,22 @@
fun testUpdateDeviceItems_hasCachedDevice_filterMatch_returnMultipleDeviceItem() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ false }, deviceItem1), createFactory({ true }, deviceItem2))
- )
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory({ false }, deviceItem1),
+ createFactory({ true }, deviceItem2)
+ ),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
@@ -176,18 +207,31 @@
fun testUpdateDeviceItems_sortByDisplayPriority() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(
- createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
- createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device1 },
+ deviceItem1
+ ),
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device2 },
+ deviceItem2
+ )
+ ),
+ listOf(
+ DeviceItemType.SAVED_BLUETOOTH_DEVICE,
+ DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
+ ),
+ testScope.backgroundScope,
+ dispatcher
)
- )
- interactor.setDisplayPriorityForTesting(
- listOf(
- DeviceItemType.SAVED_BLUETOOTH_DEVICE,
- DeviceItemType.CONNECTED_BLUETOOTH_DEVICE
- )
- )
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
@@ -204,15 +248,28 @@
fun testUpdateDeviceItems_sameType_sortByRecentlyConnected() {
testScope.runTest {
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(listOf(device2, device1))
- interactor.setDeviceItemFactoryListForTesting(
- listOf(
- createFactory({ cachedDevice -> cachedDevice.device == device1 }, deviceItem1),
- createFactory({ cachedDevice -> cachedDevice.device == device2 }, deviceItem2)
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device1 },
+ deviceItem1
+ ),
+ createFactory(
+ { cachedDevice -> cachedDevice.device == device2 },
+ deviceItem2
+ )
+ ),
+ listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE),
+ testScope.backgroundScope,
+ dispatcher
)
- )
- interactor.setDisplayPriorityForTesting(
- listOf(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
- )
`when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
`when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
@@ -231,10 +288,19 @@
`when`(bluetoothTileDialogRepository.cachedDevices)
.thenReturn(listOf(cachedDevice2, cachedDevice2, cachedDevice2, cachedDevice2))
`when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
- interactor.setDeviceItemFactoryListForTesting(
- listOf(createFactory({ true }, deviceItem2))
- )
-
+ interactor =
+ DeviceItemInteractor(
+ bluetoothTileDialogRepository,
+ audioManager,
+ adapter,
+ localBluetoothManager,
+ fakeSystemClock,
+ logger,
+ listOf(createFactory({ true }, deviceItem2)),
+ emptyList(),
+ testScope.backgroundScope,
+ dispatcher
+ )
val latest by collectLastValue(interactor.deviceItemUpdate)
val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)