summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Haijie Hong <hahong@google.com> 2025-01-10 21:17:13 +0800
committer Haijie Hong <hahong@google.com> 2025-01-10 21:17:56 +0800
commit1be83fe241750e4682128c90f5f4b7fe0b46923f (patch)
treee3def5cd251638bbb77729ee16332a768ff4c487
parent4b1b17399ec946c716e2efa9626cced7507a56ff (diff)
Show two volume slider when audio sharing
1. Show an extra volume slider when audio sharing 2. Modify media slider icon and text when audio sharing BUG: 336183611 Test: atest AudioSharingInteractorTest Flag: com.android.settingslib.flags.enable_le_audio_sharing Change-Id: I2fcfb8973a15f8c0e986de39a55e7862990e446f
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt65
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt91
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt130
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothManagerKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothProfileManagerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt2
20 files changed, 602 insertions, 49 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index 4c4ce2a61851..01bf0c8335d1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -27,6 +27,7 @@ import android.provider.Settings
import androidx.annotation.IntRange
import com.android.internal.util.ConcurrentUtils
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.onBroadcastStartedOrStopped
import com.android.settingslib.bluetooth.onProfileConnectionStateChanged
@@ -71,6 +72,12 @@ interface AudioSharingRepository {
/** The secondary headset groupId in audio sharing. */
val secondaryGroupId: StateFlow<Int>
+ /** Primary audio sharing device. */
+ val primaryDevice: StateFlow<CachedBluetoothDevice?>
+
+ /** Secondary audio sharing device. */
+ val secondaryDevice: StateFlow<CachedBluetoothDevice?>
+
/** The headset groupId to volume map during audio sharing. */
val volumeMap: StateFlow<GroupIdToVolumes>
@@ -144,12 +151,31 @@ class AudioSharingRepositoryImpl(
)
override val secondaryGroupId: StateFlow<Int> =
- merge(
+ secondaryDevice
+ .map { BluetoothUtils.getGroupId(it) }
+ .onEach { logger.onSecondaryGroupIdChanged(it) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+ )
+
+ override val primaryDevice: StateFlow<CachedBluetoothDevice?>
+ get() = primaryGroupId.map { getCachedDeviceFromGroupId(it) }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(),
+ null
+ )
+
+ override val secondaryDevice: StateFlow<CachedBluetoothDevice?>
+ get() = merge(
isAudioSharingProfilesReady.flatMapLatest { ready ->
if (ready) {
btManager.profileManager.leAudioBroadcastAssistantProfile
.onSourceConnectedOrRemoved
- .map { getSecondaryGroupId() }
+ .map { getSecondaryDevice() }
} else {
emptyFlow()
}
@@ -160,15 +186,14 @@ class AudioSharingRepositoryImpl(
profileConnection.bluetoothProfile ==
BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
}
- .map { getSecondaryGroupId() },
- primaryGroupId.map { getSecondaryGroupId() })
- .onStart { emit(getSecondaryGroupId()) }
- .onEach { logger.onSecondaryGroupIdChanged(it) }
+ .map { getSecondaryDevice() },
+ primaryGroupId.map { getSecondaryDevice() })
+ .onStart { emit(getSecondaryDevice()) }
.flowOn(backgroundCoroutineContext)
.stateIn(
coroutineScope,
SharingStarted.WhileSubscribed(),
- BluetoothCsipSetCoordinator.GROUP_ID_INVALID
+ null
)
override val volumeMap: StateFlow<GroupIdToVolumes> =
@@ -257,10 +282,24 @@ class AudioSharingRepositoryImpl(
private fun isBroadcasting(): Boolean =
btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false
- private fun getSecondaryGroupId(): Int =
- BluetoothUtils.getGroupId(
- BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
- )
+ private fun getSecondaryDevice(): CachedBluetoothDevice? =
+ BluetoothUtils.getSecondaryDeviceForBroadcast(contentResolver, btManager)
+
+ private suspend fun getCachedDeviceFromGroupId(groupId: Int): CachedBluetoothDevice? =
+ withContext(backgroundCoroutineContext) {
+ btManager
+ .profileManager
+ ?.leAudioBroadcastAssistantProfile
+ ?.allConnectedDevices
+ ?.firstNotNullOfOrNull { device ->
+ val cachedDevice = btManager.cachedDeviceManager.findDevice(device)
+ if (BluetoothUtils.getGroupId(cachedDevice) == groupId) {
+ cachedDevice
+ } else {
+ null
+ }
+ }
+ }
}
class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
@@ -269,6 +308,10 @@ class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
override val secondaryGroupId: StateFlow<Int> =
MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
+ override val primaryDevice: StateFlow<CachedBluetoothDevice?>
+ get() = MutableStateFlow(null)
+ override val secondaryDevice: StateFlow<CachedBluetoothDevice?>
+ get() = MutableStateFlow(null)
override val volumeMap: StateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override suspend fun audioSharingAvailable(): Boolean = false
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
index 8c5a0851cc92..0f25ae208a69 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt
@@ -209,6 +209,21 @@ class AudioSharingRepositoryTest {
}
@Test
+ fun primaryDeviceChange_emitValues() {
+ testScope.runTest {
+ `when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
+
+ val devices = mutableListOf<CachedBluetoothDevice?>()
+ underTest.primaryDevice.onEach { devices.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerContentObserverChange()
+ runCurrent()
+
+ Truth.assertThat(devices).containsExactly(null, cachedDevice2)
+ }
+ }
+
+ @Test
fun secondaryGroupIdChange_profileNotReady_assistantCallbackNotRegistered() {
testScope.runTest {
val groupIds = mutableListOf<Int?>()
@@ -269,6 +284,29 @@ class AudioSharingRepositoryTest {
}
@Test
+ fun secondaryDeviceChange_emitValues() {
+ testScope.runTest {
+ `when`(broadcast.isProfileReady).thenReturn(true)
+ `when`(assistant.isProfileReady).thenReturn(true)
+ `when`(volumeControl.isProfileReady).thenReturn(true)
+ val devices = mutableListOf<CachedBluetoothDevice?>()
+ underTest.secondaryDevice.onEach { devices.add(it) }.launchIn(backgroundScope)
+ runCurrent()
+ triggerSourceAdded()
+ runCurrent()
+ triggerContentObserverChange()
+ runCurrent()
+
+ Truth.assertThat(devices)
+ .containsExactly(
+ null,
+ cachedDevice2,
+ cachedDevice1,
+ )
+ }
+ }
+
+ @Test
fun volumeMapChange_profileReady_emitValues() {
testScope.runTest {
`when`(broadcast.isProfileReady).thenReturn(true)
@@ -363,7 +401,7 @@ class AudioSharingRepositoryTest {
TEST_GROUP_ID1
)
`when`(assistant.allConnectedDevices).thenReturn(listOf(device1, device2))
- assistantCallbackCaptor.value.sourceAdded(device1, receiveState)
+ assistantCallbackCaptor.value.sourceAdded(device1)
}
private fun triggerSourceRemoved() {
@@ -432,11 +470,9 @@ class AudioSharingRepositoryTest {
onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID)
}
val sourceAdded:
- BluetoothLeBroadcastAssistant.Callback.(
- sink: BluetoothDevice, state: BluetoothLeBroadcastReceiveState
- ) -> Unit =
- { sink, state ->
- onReceiveStateChanged(sink, TEST_SOURCE_ID, state)
+ BluetoothLeBroadcastAssistant.Callback.(sink: BluetoothDevice) -> Unit =
+ { sink ->
+ onSourceAdded(sink, TEST_SOURCE_ID, TEST_REASON)
}
val sourceRemoved: BluetoothLeBroadcastAssistant.Callback.(sink: BluetoothDevice) -> Unit =
{ sink ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index c9d147b6c81c..09d6ac6589ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -16,11 +16,16 @@
package com.android.systemui.volume.domain.interactor
+import android.bluetooth.BluetoothDevice
import android.media.AudioManager.STREAM_MUSIC
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.cachedBluetoothDeviceManager
+import com.android.systemui.bluetooth.localBluetoothProfileManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
@@ -32,6 +37,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@@ -39,10 +46,23 @@ import org.junit.runner.RunWith
class AudioSharingInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
lateinit var underTest: AudioSharingInteractor
+ private val bluetoothDevice: BluetoothDevice = mock {}
+ private val cachedDevice: CachedBluetoothDevice = mock {
+ on { groupId }.thenReturn(TEST_GROUP_ID)
+ on { device }.thenReturn(bluetoothDevice)
+ }
@Before
fun setUp() {
with(kosmos) {
+ whenever(cachedBluetoothDeviceManager.findDevice(bluetoothDevice))
+ .thenReturn(cachedDevice)
+ val broadcastAssistantProfile: LocalBluetoothLeBroadcastAssistant = mock {
+ on { allConnectedDevices }.thenReturn(listOf(bluetoothDevice))
+ }
+ whenever(localBluetoothProfileManager.leAudioBroadcastAssistantProfile)
+ .thenReturn(broadcastAssistantProfile)
+
with(audioSharingRepository) { setVolumeMap(mapOf(TEST_GROUP_ID to TEST_VOLUME)) }
underTest = audioSharingInteractor
}
@@ -90,6 +110,35 @@ class AudioSharingInteractorTest : SysuiTestCase() {
}
@Test
+ fun getPrimaryDevice() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setPrimaryDevice(cachedDevice) }
+ underTest.handlePrimaryGroupChange()
+
+ val primaryDevice by collectLastValue(underTest.primaryDevice)
+ runCurrent()
+
+ Truth.assertThat(primaryDevice).isEqualTo(cachedDevice)
+ }
+ }
+ }
+
+ @Test
+ fun getSecondaryDevice() {
+ with(kosmos) {
+ testScope.runTest {
+ with(audioSharingRepository) { setSecondaryDevice(cachedDevice) }
+
+ val secondaryDevice by collectLastValue(underTest.secondaryDevice)
+ runCurrent()
+
+ Truth.assertThat(secondaryDevice).isEqualTo(cachedDevice)
+ }
+ }
+ }
+
+ @Test
fun handlePrimaryGroupChange_setStreamVolume() {
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
new file mode 100644
index 000000000000..b34d7b8ec17d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.bluetooth.BluetoothDevice
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AudioSharingStreamSliderViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var stream: AudioSharingStreamSliderViewModel
+
+ @Before
+ fun setUp() {
+ stream = audioSharingStreamSliderViewModel()
+ }
+
+ private fun audioSharingStreamSliderViewModel(): AudioSharingStreamSliderViewModel {
+ return AudioSharingStreamSliderViewModel(
+ testScope.backgroundScope,
+ context,
+ kosmos.audioSharingInteractor,
+ kosmos.uiEventLogger,
+ kosmos.sliderHapticsViewModelFactory,
+ )
+ }
+
+ @Test
+ fun slider_media_inAudioSharing() =
+ with(kosmos) {
+ testScope.runTest {
+ val audioSharingSlider by collectLastValue(stream.slider)
+
+ val bluetoothDevice: BluetoothDevice = mock {}
+ val cachedDevice: CachedBluetoothDevice = mock {
+ on { groupId }.thenReturn(123)
+ on { device }.thenReturn(bluetoothDevice)
+ on { name }.thenReturn("my headset 2")
+ }
+ audioSharingRepository.setSecondaryDevice(cachedDevice)
+
+ audioSharingRepository.setInAudioSharing(true)
+ audioSharingRepository.setSecondaryGroupId(123)
+
+ runCurrent()
+
+ assertThat(audioSharingSlider!!.label).isEqualTo("my headset 2")
+ assertThat(audioSharingSlider!!.icon)
+ .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
index 51cac6976362..9e8cde3bc936 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
@@ -23,19 +23,27 @@ import android.platform.test.annotations.EnableFlags
import android.service.notification.ZenPolicy
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.audioSharingRepository
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -146,4 +154,25 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
assertThat(notificationSlider!!.disabledMessage)
.isEqualTo("Unavailable because ring is muted")
}
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_SHOW_AUDIO_SHARING_SLIDER_IN_VOLUME_PANEL)
+ fun slider_media_inAudioSharing() =
+ kosmos.runTest {
+ val mediaSlider by
+ collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_MUSIC).slider)
+
+ val cachedDevice: CachedBluetoothDevice = mock {
+ on { groupId }.thenReturn(123)
+ on { name }.thenReturn("my headset 1")
+ }
+
+ audioSharingRepository.setInAudioSharing(true)
+ audioSharingRepository.setPrimaryDevice(cachedDevice)
+ runCurrent()
+
+ assertThat(mediaSlider!!.label).isEqualTo("my headset 1")
+ assertThat(mediaSlider!!.icon)
+ .isEqualTo(Icon.Resource(R.drawable.ic_volume_media_bt, null))
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 7da041e7ef19..411288ff1274 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -22,6 +22,7 @@ import android.media.AudioManager.STREAM_MUSIC
import androidx.annotation.IntRange
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.bluetooth.BluetoothUtils
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.flags.Flags
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository.Companion.AUDIO_SHARING_VOLUME_MAX
@@ -42,12 +43,19 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface AudioSharingInteractor {
/** Audio sharing state on the device. */
val isInAudioSharing: Flow<Boolean>
+ /** Primary audio sharing device. */
+ val primaryDevice: Flow<CachedBluetoothDevice?>
+
+ /** Secondary audio sharing device. */
+ val secondaryDevice: Flow<CachedBluetoothDevice?>
+
/** Audio sharing secondary headset volume changes. */
val volume: Flow<Int?>
@@ -86,6 +94,11 @@ constructor(
private val audioSharingRepository: AudioSharingRepository,
) : AudioSharingInteractor {
override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
+ override val primaryDevice: Flow<CachedBluetoothDevice?>
+ get() = audioSharingRepository.primaryDevice
+
+ override val secondaryDevice: Flow<CachedBluetoothDevice?>
+ get() = audioSharingRepository.secondaryDevice
override val volume: Flow<Int?> =
combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
@@ -148,6 +161,8 @@ constructor(
@SysUISingleton
class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
override val isInAudioSharing: Flow<Boolean> = flowOf(false)
+ override val primaryDevice: Flow<CachedBluetoothDevice?> = flowOf(null)
+ override val secondaryDevice: Flow<CachedBluetoothDevice?> = flowOf(null)
override val volume: Flow<Int?> = emptyFlow()
override val volumeMin: Int = EMPTY_VOLUME
override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
index 1e4afc0ac5fe..a326da45a7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -21,6 +21,7 @@ import com.android.settingslib.volume.data.repository.AudioSystemRepository
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.Flags
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
@@ -44,6 +45,7 @@ constructor(
mediaOutputInteractor: MediaOutputInteractor,
audioModeInteractor: AudioModeInteractor,
private val audioSystemRepository: AudioSystemRepository,
+ audioSharingInteractor: AudioSharingInteractor,
) {
val volumePanelSliders: StateFlow<List<SliderType>> =
@@ -51,7 +53,8 @@ constructor(
mediaOutputInteractor.activeMediaDeviceSessions,
mediaOutputInteractor.defaultActiveMediaSession.filterData(),
audioModeInteractor.isOngoingCall,
- ) { activeSessions, defaultSession, isOngoingCall ->
+ audioSharingInteractor.volume,
+ ) { activeSessions, defaultSession, isOngoingCall, audioSharingVolume ->
coroutineScope {
val viewModels = buildList {
if (isOngoingCall) {
@@ -61,8 +64,14 @@ constructor(
if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
addSession(activeSessions.remote)
addStream(AudioManager.STREAM_MUSIC)
+ if (Flags.showAudioSharingSliderInVolumePanel()) {
+ audioSharingVolume?.let { addAudioSharingStream() }
+ }
} else {
addStream(AudioManager.STREAM_MUSIC)
+ if (Flags.showAudioSharingSliderInVolumePanel()) {
+ audioSharingVolume?.let { addAudioSharingStream() }
+ }
addSession(activeSessions.remote)
}
@@ -89,13 +98,18 @@ constructor(
// Hide other streams except STREAM_MUSIC if the isSingleVolume mode is on. This makes sure
// the volume slider in volume panel is consistent with the volume slider inside system
// settings app.
- if (Flags.onlyShowMediaStreamSliderInSingleVolumeMode() &&
- audioSystemRepository.isSingleVolume &&
- stream != AudioManager.STREAM_MUSIC
+ if (
+ Flags.onlyShowMediaStreamSliderInSingleVolumeMode() &&
+ audioSystemRepository.isSingleVolume &&
+ stream != AudioManager.STREAM_MUSIC
) {
return
}
add(SliderType.Stream(AudioStream(stream)))
}
+
+ private fun MutableList<SliderType>.addAudioSharingStream() {
+ add(SliderType.AudioSharingStream)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
index 6129ce543e2e..f180744eac22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/model/SliderType.kt
@@ -27,4 +27,7 @@ sealed interface SliderType {
/** The represents media device casting volume. */
data class MediaDeviceCast(val session: MediaDeviceSession) : SliderType
+
+ /** Represents the audio sharing volume stream. */
+ data object AudioSharingStream : SliderType
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
new file mode 100644
index 000000000000..4ce9fe561aed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModel.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.Context
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.Flags
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
+import com.android.systemui.res.R
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class AudioSharingStreamSliderViewModel
+@AssistedInject
+constructor(
+ @Assisted private val coroutineScope: CoroutineScope,
+ private val context: Context,
+ private val audioSharingInteractor: AudioSharingInteractor,
+ private val uiEventLogger: UiEventLogger,
+ private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+) : SliderViewModel {
+ private val volumeChanges = MutableStateFlow<Int?>(null)
+
+ override val slider: StateFlow<SliderState> =
+ combine(audioSharingInteractor.volume, audioSharingInteractor.secondaryDevice) {
+ volume,
+ device ->
+ val deviceName = device?.name ?: return@combine SliderState.Empty
+ if (volume == null) {
+ SliderState.Empty
+ } else {
+ State(
+ value = volume.toFloat(),
+ valueRange =
+ audioSharingInteractor.volumeMin.toFloat()..audioSharingInteractor
+ .volumeMax
+ .toFloat(),
+ icon = Icon.Resource(R.drawable.ic_volume_media_bt, null),
+ label = deviceName,
+ )
+ }
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
+
+ init {
+ volumeChanges
+ .filterNotNull()
+ .onEach { audioSharingInteractor.setStreamVolume(it) }
+ .launchIn(coroutineScope)
+ }
+
+ override fun onValueChanged(state: SliderState, newValue: Float) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ volumeChanges.tryEmit(newValue.roundToInt())
+ }
+
+ override fun onValueChangeFinished() {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_SHARING_SLIDER_TOUCHED)
+ }
+
+ override fun toggleMuted(state: SliderState) {}
+
+ override fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory? =
+ if (Flags.hapticsForComposeSliders() && slider.value != SliderState.Empty) {
+ hapticsViewModelFactory
+ } else {
+ null
+ }
+
+ private data class State(
+ override val value: Float,
+ override val valueRange: ClosedFloatingPointRange<Float>,
+ override val icon: Icon,
+ override val label: String,
+ ) : SliderState {
+ override val isEnabled: Boolean
+ get() = true
+
+ override val a11yStep: Int
+ get() = 1
+
+ override val disabledMessage: String?
+ get() = null
+
+ override val isMutable: Boolean
+ get() = false
+
+ override val a11yClickDescription: String?
+ get() = null
+
+ override val a11yStateDescription: String?
+ get() = null
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 5b8d9b045475..3f5b899d27f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -21,6 +21,7 @@ import android.media.AudioManager
import android.util.Log
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
@@ -31,6 +32,8 @@ import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
@@ -42,7 +45,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
@@ -59,6 +61,7 @@ constructor(
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
private val zenModeInteractor: ZenModeInteractor,
+ private val audioSharingInteractor: AudioSharingInteractor,
private val uiEventLogger: UiEventLogger,
private val volumePanelLogger: VolumePanelLogger,
private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
@@ -68,14 +71,6 @@ constructor(
private val streamsAffectedByRing =
setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
private val audioStream = audioStreamWrapper.audioStream
- private val iconsByStream =
- mapOf(
- AudioStream(AudioManager.STREAM_MUSIC) to R.drawable.ic_music_note,
- AudioStream(AudioManager.STREAM_VOICE_CALL) to R.drawable.ic_call,
- AudioStream(AudioManager.STREAM_RING) to R.drawable.ic_ring_volume,
- AudioStream(AudioManager.STREAM_NOTIFICATION) to R.drawable.ic_volume_ringer,
- AudioStream(AudioManager.STREAM_ALARM) to R.drawable.ic_volume_alarm,
- )
private val labelsByStream =
mapOf(
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_music,
@@ -104,9 +99,18 @@ constructor(
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
streamDisabledMessage(),
- ) { model, isEnabled, ringerMode, streamDisabledMessage ->
+ audioSharingInteractor.isInAudioSharing,
+ audioSharingInteractor.primaryDevice,
+ ) { model, isEnabled, ringerMode, streamDisabledMessage, isInAudioSharing, primaryDevice
+ ->
volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
- model.toState(isEnabled, ringerMode, streamDisabledMessage)
+ model.toState(
+ isEnabled,
+ ringerMode,
+ streamDisabledMessage,
+ isInAudioSharing,
+ primaryDevice,
+ )
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -149,14 +153,15 @@ constructor(
isEnabled: Boolean,
ringerMode: RingerMode,
disabledMessage: String?,
+ inAudioSharing: Boolean,
+ primaryDevice: CachedBluetoothDevice?,
): State {
- val label =
- labelsByStream[audioStream]?.let(context::getString)
- ?: error("No label for the stream: $audioStream")
+ val label = getLabel(inAudioSharing, primaryDevice)
+ val icon = getIcon(ringerMode, inAudioSharing)
return State(
value = volume.toFloat(),
valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
- icon = getIcon(ringerMode),
+ icon = icon,
label = label,
disabledMessage = disabledMessage,
isEnabled = isEnabled,
@@ -224,7 +229,22 @@ constructor(
}
}
- private fun AudioStreamModel.getIcon(ringerMode: RingerMode): Icon {
+ private fun AudioStreamModel.getLabel(
+ inAudioSharing: Boolean,
+ primaryDevice: CachedBluetoothDevice?,
+ ): String =
+ if (
+ Flags.showAudioSharingSliderInVolumePanel() &&
+ audioStream.value == AudioManager.STREAM_MUSIC &&
+ inAudioSharing
+ ) {
+ primaryDevice?.name ?: context.getString(R.string.stream_music)
+ } else {
+ labelsByStream[audioStream]?.let(context::getString)
+ ?: error("No label for the stream: $audioStream")
+ }
+
+ private fun AudioStreamModel.getIcon(ringerMode: RingerMode, inAudioSharing: Boolean): Icon {
val iconRes =
if (isAffectedByMute && isMuted) {
if (audioStream.value in streamsAffectedByRing) {
@@ -234,18 +254,36 @@ constructor(
R.drawable.ic_volume_off
}
} else {
- R.drawable.ic_volume_off
+ if (
+ Flags.showAudioSharingSliderInVolumePanel() &&
+ audioStream.value == AudioManager.STREAM_MUSIC &&
+ inAudioSharing
+ ) {
+ R.drawable.ic_volume_media_bt_mute
+ } else R.drawable.ic_volume_off
}
} else {
- iconsByStream[audioStream]
- ?: run {
- Log.wtf(TAG, "No icon for the stream: $audioStream")
- R.drawable.ic_music_note
- }
+ getIconByStream(audioStream, inAudioSharing)
}
return Icon.Resource(iconRes, null)
}
+ private fun getIconByStream(audioStream: AudioStream, inAudioSharing: Boolean): Int =
+ when (audioStream.value) {
+ AudioManager.STREAM_MUSIC ->
+ if (Flags.showAudioSharingSliderInVolumePanel() && inAudioSharing) {
+ R.drawable.ic_volume_media_bt
+ } else R.drawable.ic_music_note
+ AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_call
+ AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
+ AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm
+ else -> {
+ Log.wtf(TAG, "No icon for the stream: $audioStream")
+ R.drawable.ic_music_note
+ }
+ }
+
private val AudioStreamModel.volumeRange: IntRange
get() = minVolume..maxVolume
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 96afbc1feaaf..28f11050fa4a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.component.volume.ui.viewmodel
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
@@ -23,6 +24,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioSharingStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
@@ -45,7 +47,6 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Controls the behaviour of the whole audio
@@ -61,6 +62,7 @@ constructor(
mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+ private val audioSharingStreamSliderViewModelFactory: AudioSharingStreamSliderViewModel.Factory,
audioModeInteractor: AudioModeInteractor,
streamsInteractor: AudioSlidersInteractor,
) {
@@ -108,6 +110,7 @@ constructor(
is SliderType.Stream -> createStreamViewModel(type.stream)
is SliderType.MediaDeviceCast ->
createSessionViewModel(type.session)
+ is SliderType.AudioSharingStream -> createAudioSharingViewModel()
}
}
emit(viewModels)
@@ -138,11 +141,15 @@ constructor(
}
private fun CoroutineScope.createStreamViewModel(
- stream: AudioStream,
+ stream: AudioStream
): AudioStreamSliderViewModel {
return streamSliderViewModelFactory.create(
AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
this,
)
}
+
+ private fun CoroutineScope.createAudioSharingViewModel(): AudioSharingStreamSliderViewModel {
+ return audioSharingStreamSliderViewModelFactory.create(this)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
index 8b8714fcca8c..d3a4fe86e827 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
@@ -34,6 +34,8 @@ enum class VolumePanelUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "The notification volume slider is touched")
VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED(1642),
@UiEvent(doc = "The alarm volume slider is touched") VOLUME_PANEL_ALARM_SLIDER_TOUCHED(1643),
+ @UiEvent(doc = "The audio sharing volume slider is touched")
+ VOLUME_PANEL_AUDIO_SHARING_SLIDER_TOUCHED(2068),
@UiEvent(doc = "Live caption toggle is shown") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN(1644),
@UiEvent(doc = "Live caption toggle is gone") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE(1645),
@UiEvent(doc = "Live caption toggle is clicked") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED(1646),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothManagerKosmos.kt
index eef89e7dac68..3d58cf58d7b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothManagerKosmos.kt
@@ -18,10 +18,12 @@ package com.android.systemui.bluetooth
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
+import org.mockito.kotlin.mock
var Kosmos.localBluetoothManager: LocalBluetoothManager? by
Kosmos.Fixture {
- mock { whenever(cachedDeviceManager).thenReturn(cachedBluetoothDeviceManager) }
+ mock {
+ on { cachedDeviceManager }.thenReturn(cachedBluetoothDeviceManager)
+ on { profileManager }.thenReturn(localBluetoothProfileManager)
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothProfileManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothProfileManagerKosmos.kt
new file mode 100644
index 000000000000..34d7848a70aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/LocalBluetoothProfileManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.localBluetoothProfileManager: LocalBluetoothProfileManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index 5da6ee95234c..6e76cf34c652 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.data.repository
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.data.repository.GroupIdToVolumes
import kotlinx.coroutines.flow.MutableStateFlow
@@ -28,11 +29,17 @@ class FakeAudioSharingRepository : AudioSharingRepository {
MutableStateFlow(TEST_GROUP_ID_INVALID)
private val mutableSecondaryGroupId: MutableStateFlow<Int> =
MutableStateFlow(TEST_GROUP_ID_INVALID)
+ private val mutablePrimaryDevice: MutableStateFlow<CachedBluetoothDevice?> =
+ MutableStateFlow(null)
+ private val mutableSecondaryDevice: MutableStateFlow<CachedBluetoothDevice?> =
+ MutableStateFlow(null)
private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
override val inAudioSharing: StateFlow<Boolean> = mutableInAudioSharing
override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId
override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
+ override val primaryDevice: StateFlow<CachedBluetoothDevice?> = mutablePrimaryDevice
+ override val secondaryDevice: StateFlow<CachedBluetoothDevice?> = mutableSecondaryDevice
override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
override suspend fun audioSharingAvailable(): Boolean = mutableAvailable
@@ -55,6 +62,14 @@ class FakeAudioSharingRepository : AudioSharingRepository {
mutableSecondaryGroupId.value = groupId
}
+ fun setPrimaryDevice(device: CachedBluetoothDevice?) {
+ mutablePrimaryDevice.value = device
+ }
+
+ fun setSecondaryDevice(device: CachedBluetoothDevice?) {
+ mutableSecondaryDevice.value = device
+ }
+
fun setVolumeMap(volumeMap: GroupIdToVolumes) {
mutableVolumeMap.value = volumeMap
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt
index 1fb5e77a3210..78cafbf99f49 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/FakeAudioSharingInteractor.kt
@@ -19,10 +19,11 @@ package com.android.systemui.volume.domain.interactor
import android.content.Context
import androidx.annotation.IntRange
import com.android.dream.lowlight.dagger.qualifiers.Application
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeAudioSharingInteractor : AudioSharingInteractor {
+class FakeAudioSharingInteractor() : AudioSharingInteractor {
private val mutableInAudioSharing: MutableStateFlow<Boolean> = MutableStateFlow(false)
private val mutableVolume: MutableStateFlow<Int?> = MutableStateFlow(null)
private var audioSharingVolumeBarAvailable = false
@@ -31,6 +32,8 @@ class FakeAudioSharingInteractor : AudioSharingInteractor {
override val volume: Flow<Int?> = mutableVolume
override val volumeMin: Int = AUDIO_SHARING_VOLUME_MIN
override val volumeMax: Int = AUDIO_SHARING_VOLUME_MAX
+ override val primaryDevice = MutableStateFlow<CachedBluetoothDevice?>(null)
+ override val secondaryDevice = MutableStateFlow<CachedBluetoothDevice?>(null)
override suspend fun audioSharingVolumeBarAvailable(@Application context: Context): Boolean =
audioSharingVolumeBarAvailable
@@ -54,6 +57,14 @@ class FakeAudioSharingInteractor : AudioSharingInteractor {
audioSharingVolumeBarAvailable = available
}
+ fun setPrimaryDevice(device: CachedBluetoothDevice?) {
+ primaryDevice.value = device
+ }
+
+ fun setSecondaryDevice(device: CachedBluetoothDevice?) {
+ secondaryDevice.value = device
+ }
+
companion object {
const val AUDIO_SHARING_VOLUME_MIN = 0
const val AUDIO_SHARING_VOLUME_MAX = 255
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
index 3bc920edd948..88734cd3b000 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.volume.data.repository.audioSystemRepository
import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.android.systemui.volume.mediaOutputInteractor
val Kosmos.audioSlidersInteractor by
@@ -29,5 +30,6 @@ val Kosmos.audioSlidersInteractor by
mediaOutputInteractor,
audioModeInteractor,
audioSystemRepository,
+ audioSharingInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
new file mode 100644
index 000000000000..96bc9722635a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.volume.panel.component.volume.slider.ui.viewmodel
+
+import android.content.applicationContext
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
+import kotlinx.coroutines.CoroutineScope
+
+val Kosmos.audioSharingStreamSliderViewModelFactory by
+ Kosmos.Fixture {
+ object : AudioSharingStreamSliderViewModel.Factory {
+ override fun create(coroutineScope: CoroutineScope): AudioSharingStreamSliderViewModel {
+ return AudioSharingStreamSliderViewModel(
+ coroutineScope,
+ applicationContext,
+ audioSharingInteractor,
+ uiEventLogger,
+ sliderHapticsViewModelFactory,
+ )
+ }
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index a78670d7f1cc..88c716e0ab10 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -21,6 +21,7 @@ import com.android.internal.logging.uiEventLogger
import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
@@ -39,6 +40,7 @@ val Kosmos.audioStreamSliderViewModelFactory by
applicationContext,
audioVolumeInteractor,
zenModeInteractor,
+ audioSharingInteractor,
uiEventLogger,
volumePanelLogger,
sliderHapticsViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
index 6e848ce26d9b..a6a4f8368941 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.volume.domain.interactor.audioModeInteractor
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.component.volume.domain.interactor.audioSlidersInteractor
+import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.audioSharingStreamSliderViewModelFactory
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.audioStreamSliderViewModelFactory
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.castVolumeSliderViewModelFactory
@@ -33,6 +34,7 @@ val Kosmos.audioVolumeComponentViewModel by
mediaDeviceSessionInteractor,
audioStreamSliderViewModelFactory,
castVolumeSliderViewModelFactory,
+ audioSharingStreamSliderViewModelFactory,
audioModeInteractor,
audioSlidersInteractor,
)