diff options
2 files changed, 61 insertions, 118 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt index 4c329dcf2f2b..cebd05d92537 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt @@ -18,20 +18,12 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothLeBroadcast import android.bluetooth.BluetoothLeBroadcastMetadata -import android.content.ContentResolver -import android.content.applicationContext import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.bluetooth.BluetoothEventManager import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast -import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager -import com.android.settingslib.bluetooth.VolumeControlProfile -import com.android.settingslib.volume.shared.AudioSharingLogger import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -46,14 +38,10 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.kotlin.any -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.whenever @@ -78,7 +66,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testIsAudioSharingOn_flagOff_false() = + fun isAudioSharingOn_flagOff_false() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(false) @@ -90,7 +78,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testIsAudioSharingOn_flagOn_notInAudioSharing_false() = + fun isAudioSharingOn_flagOn_notInAudioSharing_false() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) @@ -103,7 +91,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testIsAudioSharingOn_flagOn_inAudioSharing_true() = + fun isAudioSharingOn_flagOn_inAudioSharing_true() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) @@ -116,7 +104,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testAudioSourceStateUpdate_notInAudioSharing_returnEmpty() = + fun audioSourceStateUpdate_notInAudioSharing_returnEmpty() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) @@ -129,7 +117,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testAudioSourceStateUpdate_inAudioSharing_returnUnit() = + fun audioSourceStateUpdate_inAudioSharing_returnUnit() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) @@ -144,7 +132,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testHandleAudioSourceWhenReady_flagOff_sourceNotAdded() = + fun handleAudioSourceWhenReady_flagOff_sourceNotAdded() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(false) @@ -157,7 +145,7 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testHandleAudioSourceWhenReady_noProfile_sourceNotAdded() = + fun handleAudioSourceWhenReady_noProfile_sourceNotAdded() = with(kosmos) { testScope.runTest { bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) @@ -171,36 +159,41 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testHandleAudioSourceWhenReady_hasProfileButAudioSharingOff_sourceNotAdded() = + fun handleAudioSourceWhenReady_hasProfileButAudioSharingNeverTriggered_sourceNotAdded() = with(kosmos) { testScope.runTest { - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true) bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile( localBluetoothLeBroadcast ) val job = launch { underTest.handleAudioSourceWhenReady() } runCurrent() - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false) - runCurrent() + // Verify callback registered for onBroadcastStartedOrStopped + verify(localBluetoothLeBroadcast).registerServiceCallBack(any(), any()) assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse() job.cancel() } } @Test - fun testHandleAudioSourceWhenReady_audioSharingOnButNoPlayback_sourceNotAdded() = + fun handleAudioSourceWhenReady_audioSharingTriggeredButFailed_sourceNotAdded() = with(kosmos) { testScope.runTest { - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false) bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile( localBluetoothLeBroadcast ) val job = launch { underTest.handleAudioSourceWhenReady() } runCurrent() - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true) + // Verify callback registered for onBroadcastStartedOrStopped + verify(localBluetoothLeBroadcast) + .registerServiceCallBack(any(), callbackCaptor.capture()) + // Audio sharing started failed, trigger onBroadcastStartFailed + whenever(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(false) + underTest.startAudioSharing() + runCurrent() + callbackCaptor.value.onBroadcastStartFailed(0) runCurrent() assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse() @@ -209,122 +202,59 @@ class AudioSharingInteractorTest : SysuiTestCase() { } @Test - fun testHandleAudioSourceWhenReady_audioSharingOnAndPlaybackStarts_sourceAdded() = + fun handleAudioSourceWhenReady_audioSharingTriggeredButMetadataNotReady_sourceNotAdded() = with(kosmos) { testScope.runTest { - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false) bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile( localBluetoothLeBroadcast ) val job = launch { underTest.handleAudioSourceWhenReady() } runCurrent() - bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true) - runCurrent() + // Verify callback registered for onBroadcastStartedOrStopped verify(localBluetoothLeBroadcast) .registerServiceCallBack(any(), callbackCaptor.capture()) runCurrent() - callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata) + underTest.startAudioSharing() runCurrent() + // Verify callback registered for onBroadcastMetadataChanged + verify(localBluetoothLeBroadcast, times(2)) + .registerServiceCallBack(any(), callbackCaptor.capture()) - assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isTrue() - job.cancel() - } - } - - @Test - fun testHandleAudioSourceWhenReady_skipInitialValue_noAudioSharing_sourceNotAdded() = - with(kosmos) { - testScope.runTest { - val (broadcast, repository) = setupRepositoryImpl() - val interactor = - object : - AudioSharingInteractorImpl( - applicationContext, - localBluetoothManager, - repository, - testDispatcher, - ) { - override suspend fun audioSharingAvailable() = true - } - val job = launch { interactor.handleAudioSourceWhenReady() } - runCurrent() - // Verify callback registered for onBroadcastStartedOrStopped - verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture()) - runCurrent() - // Verify source is not added - verify(repository, never()).addSource() + assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse() job.cancel() } } @Test - fun testHandleAudioSourceWhenReady_skipInitialValue_newAudioSharing_sourceAdded() = + fun handleAudioSourceWhenReady_audioSharingTriggeredAndMetadataReady_sourceAdded() = with(kosmos) { testScope.runTest { - val (broadcast, repository) = setupRepositoryImpl() - val interactor = - object : - AudioSharingInteractorImpl( - applicationContext, - localBluetoothManager, - repository, - testDispatcher, - ) { - override suspend fun audioSharingAvailable() = true - } - val job = launch { interactor.handleAudioSourceWhenReady() } + bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true) + bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile( + localBluetoothLeBroadcast + ) + val job = launch { underTest.handleAudioSourceWhenReady() } runCurrent() // Verify callback registered for onBroadcastStartedOrStopped - verify(broadcast).registerServiceCallBack(any(), callbackCaptor.capture()) + verify(localBluetoothLeBroadcast) + .registerServiceCallBack(any(), callbackCaptor.capture()) // Audio sharing started, trigger onBroadcastStarted - whenever(broadcast.isEnabled(null)).thenReturn(true) + whenever(localBluetoothLeBroadcast.isEnabled(null)).thenReturn(true) + underTest.startAudioSharing() + runCurrent() callbackCaptor.value.onBroadcastStarted(0, 0) runCurrent() // Verify callback registered for onBroadcastMetadataChanged - verify(broadcast, times(2)).registerServiceCallBack(any(), callbackCaptor.capture()) + verify(localBluetoothLeBroadcast, times(2)) + .registerServiceCallBack(any(), callbackCaptor.capture()) runCurrent() // Trigger onBroadcastMetadataChanged (ready to add source) callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata) runCurrent() - // Verify source added - verify(repository).addSource() + + assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isTrue() job.cancel() } } - - private fun setupRepositoryImpl(): Pair<LocalBluetoothLeBroadcast, AudioSharingRepositoryImpl> { - with(kosmos) { - val broadcast = - mock<LocalBluetoothLeBroadcast> { - on { isProfileReady } doReturn true - on { isEnabled(null) } doReturn false - } - val assistant = - mock<LocalBluetoothLeBroadcastAssistant> { on { isProfileReady } doReturn true } - val volumeControl = mock<VolumeControlProfile> { on { isProfileReady } doReturn true } - val profileManager = - mock<LocalBluetoothProfileManager> { - on { leAudioBroadcastProfile } doReturn broadcast - on { leAudioBroadcastAssistantProfile } doReturn assistant - on { volumeControlProfile } doReturn volumeControl - } - whenever(localBluetoothManager.profileManager).thenReturn(profileManager) - whenever(localBluetoothManager.eventManager).thenReturn(mock<BluetoothEventManager> {}) - - val repository = - AudioSharingRepositoryImpl( - localBluetoothManager, - com.android.settingslib.volume.data.repository.AudioSharingRepositoryImpl( - mock<ContentResolver> {}, - localBluetoothManager, - testScope.backgroundScope, - testScope.testScheduler, - mock<AudioSharingLogger> {}, - ), - testDispatcher, - ) - return Pair(broadcast, spy(repository)) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt index d82311f6ca7c..832afb1799b1 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt @@ -22,20 +22,25 @@ import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.onBroadcastMetadataChanged +import com.android.settingslib.bluetooth.onBroadcastStartedOrStopped import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.withContext /** Holds business logic for the audio sharing state. */ @@ -71,6 +76,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, ) : AudioSharingInteractor { + private val audioSharingStartedEvents = Channel<Unit>(Channel.BUFFERED) private var previewEnabled: Boolean? = null override val isAudioSharingOn: Flow<Boolean> = @@ -99,12 +105,18 @@ constructor( withContext(backgroundDispatcher) { if (audioSharingAvailable()) { audioSharingRepository.leAudioBroadcastProfile?.let { profile -> - isAudioSharingOn - // Skip the default value, we only care about adding source for newly - // started audio sharing session - .drop(1) - .mapNotNull { audioSharingOn -> - if (audioSharingOn) { + merge( + // Register and start listen to onBroadcastMetadataChanged (means ready + // to add source) + audioSharingStartedEvents.receiveAsFlow().map { true }, + // When session is off or failed to start, stop listening to + // onBroadcastMetadataChanged as we won't be adding source + profile.onBroadcastStartedOrStopped + .filterNot { profile.isEnabled(null) } + .map { false }, + ) + .mapNotNull { shouldListenToMetadata -> + if (shouldListenToMetadata) { // onBroadcastMetadataChanged could emit multiple times during one // audio sharing session, we only perform add source on the first // time @@ -146,6 +158,7 @@ constructor( if (!audioSharingAvailable()) { return } + audioSharingStartedEvents.trySend(Unit) audioSharingRepository.startAudioSharing() } |