summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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,
)