Loading packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +54 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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> Loading Loading @@ -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() } Loading @@ -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> = Loading Loading @@ -257,10 +282,24 @@ class AudioSharingRepositoryImpl( private fun isBroadcasting(): Boolean = btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false private fun getSecondaryGroupId(): Int = BluetoothUtils.getGroupId( 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 { Loading @@ -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 Loading packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt +42 −6 Original line number Diff line number Diff line Loading @@ -208,6 +208,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 { Loading Loading @@ -268,6 +283,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 { Loading Loading @@ -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() { Loading Loading @@ -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 -> Loading packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt +49 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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 } Loading Loading @@ -89,6 +109,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) { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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)) } } } packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt +29 −0 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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)) } } Loading
packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt +54 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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> Loading Loading @@ -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() } Loading @@ -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> = Loading Loading @@ -257,10 +282,24 @@ class AudioSharingRepositoryImpl( private fun isBroadcasting(): Boolean = btManager.profileManager.leAudioBroadcastProfile?.isEnabled(null) ?: false private fun getSecondaryGroupId(): Int = BluetoothUtils.getGroupId( 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 { Loading @@ -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 Loading
packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioSharingRepositoryTest.kt +42 −6 Original line number Diff line number Diff line Loading @@ -208,6 +208,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 { Loading Loading @@ -268,6 +283,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 { Loading Loading @@ -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() { Loading Loading @@ -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 -> Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt +49 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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 } Loading Loading @@ -89,6 +109,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) { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioSharingStreamSliderViewModelTest.kt 0 → 100644 +91 −0 Original line number Diff line number Diff line /* * 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)) } } }
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt +29 −0 Original line number Diff line number Diff line Loading @@ -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) Loading Loading @@ -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)) } }