Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aea79856 authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge "Listen to profile readiness before registering callbacks for audio sharing." into main

parents 75eab107 ba7ef7b2
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -101,12 +101,27 @@ class AudioSharingInteractorTest : SysuiTestCase() {
            }
        }

    @Test
    fun audioSourceStateUpdate_profileNotReady_returnEmpty() =
        with(kosmos) {
            testScope.runTest {
                bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
                bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(false)
                val value by collectLastValue(underTest.audioSourceStateUpdate)
                runCurrent()

                assertThat(value).isNull()
            }
        }

    @Test
    fun audioSourceStateUpdate_notInAudioSharing_returnEmpty() =
        with(kosmos) {
            testScope.runTest {
                bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
                bluetoothTileDialogAudioSharingRepository.setInAudioSharing(false)
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val value by collectLastValue(underTest.audioSourceStateUpdate)
                runCurrent()

@@ -120,6 +135,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
            testScope.runTest {
                bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
                bluetoothTileDialogAudioSharingRepository.setInAudioSharing(true)
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val value by collectLastValue(underTest.audioSourceStateUpdate)
                runCurrent()
                bluetoothTileDialogAudioSharingRepository.emitAudioSourceStateUpdate()
@@ -158,6 +174,23 @@ class AudioSharingInteractorTest : SysuiTestCase() {
            }
        }

    @Test
    fun handleAudioSourceWhenReady_profileNotReady_sourceNotAdded() =
        with(kosmos) {
            testScope.runTest {
                bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
                bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
                    localBluetoothLeBroadcast
                )
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(false)
                val job = launch { underTest.handleAudioSourceWhenReady() }
                runCurrent()

                assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isFalse()
                job.cancel()
            }
        }

    @Test
    fun handleAudioSourceWhenReady_hasProfileButAudioSharingNeverTriggered_sourceNotAdded() =
        with(kosmos) {
@@ -166,6 +199,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
                bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
                    localBluetoothLeBroadcast
                )
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val job = launch { underTest.handleAudioSourceWhenReady() }
                runCurrent()

@@ -184,6 +218,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
                bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
                    localBluetoothLeBroadcast
                )
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val job = launch { underTest.handleAudioSourceWhenReady() }
                runCurrent()
                // Verify callback registered for onBroadcastStartedOrStopped
@@ -209,6 +244,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
                bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
                    localBluetoothLeBroadcast
                )
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val job = launch { underTest.handleAudioSourceWhenReady() }
                runCurrent()
                // Verify callback registered for onBroadcastStartedOrStopped
@@ -234,6 +270,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
                bluetoothTileDialogAudioSharingRepository.setLeAudioBroadcastProfile(
                    localBluetoothLeBroadcast
                )
                bluetoothTileDialogAudioSharingRepository.setIsAudioSharingProfilesReady(true)
                val job = launch { underTest.handleAudioSourceWhenReady() }
                runCurrent()
                // Verify callback registered for onBroadcastStartedOrStopped
+38 −3
Original line number Diff line number Diff line
@@ -24,10 +24,12 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
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.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.Before
@@ -58,12 +60,14 @@ class AudioSharingRepositoryTest : SysuiTestCase() {

    @Before
    fun setUp() {
        whenever(kosmos.localBluetoothManager.profileManager).thenReturn(profileManager)
        underTest =
            AudioSharingRepositoryImpl(
                kosmos.localBluetoothManager,
                kosmos.audioSharingRepository,
                kosmos.bluetoothTileDialogLogger,
                kosmos.testDispatcher,
                kosmos.testScope.backgroundScope,
            )
    }

@@ -91,7 +95,6 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
    fun testStartAudioSharing() =
        with(kosmos) {
            testScope.runTest {
                whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
                whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
                audioSharingRepository.setAudioSharingAvailable(true)
                underTest.startAudioSharing()
@@ -117,7 +120,6 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
    fun testStopAudioSharing() =
        with(kosmos) {
            testScope.runTest {
                whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
                whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
                audioSharingRepository.setAudioSharingAvailable(true)
                underTest.stopAudioSharing()
@@ -155,7 +157,6 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
    fun testAddSource_noMetadata_doesNothing() =
        with(kosmos) {
            testScope.runTest {
                whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
                whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
                audioSharingRepository.setAudioSharingAvailable(true)
                whenever(leAudioBroadcastProfile.latestBluetoothLeBroadcastMetadata)
@@ -212,4 +213,38 @@ class AudioSharingRepositoryTest : SysuiTestCase() {
                    .logAudioSharingRequest(AudioSharingRequest.ADD_SOURCE)
            }
        }

    @Test
    fun testIsAudioSharingProfilesReady_notReady() =
        with(kosmos) {
            testScope.runTest {
                whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
                whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
                whenever(profileManager.leAudioBroadcastAssistantProfile)
                    .thenReturn(leAudioBroadcastAssistant)
                whenever(leAudioBroadcastProfile.isProfileReady).thenReturn(false)
                whenever(leAudioBroadcastAssistant.isProfileReady).thenReturn(false)
                val value by collectLastValue(underTest.isAudioSharingProfilesReady)
                runCurrent()

                assertThat(value).isFalse()
            }
        }

    @Test
    fun testIsAudioSharingProfilesReady_ready() =
        with(kosmos) {
            testScope.runTest {
                whenever(localBluetoothManager.profileManager).thenReturn(profileManager)
                whenever(profileManager.leAudioBroadcastProfile).thenReturn(leAudioBroadcastProfile)
                whenever(profileManager.leAudioBroadcastAssistantProfile)
                    .thenReturn(leAudioBroadcastAssistant)
                whenever(leAudioBroadcastProfile.isProfileReady).thenReturn(true)
                whenever(leAudioBroadcastAssistant.isProfileReady).thenReturn(true)
                val value by collectLastValue(underTest.isAudioSharingProfilesReady)
                runCurrent()

                assertThat(value).isTrue()
            }
        }
}
+8 −5
Original line number Diff line number Diff line
@@ -31,7 +31,9 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
@@ -109,10 +111,11 @@ constructor(
    override suspend fun handleAudioSourceWhenReady() {
        withContext(backgroundDispatcher) {
            if (audioSharingAvailable()) {
                audioSharingRepository.isAudioSharingProfilesReady.filter { it }.first()
                audioSharingRepository.leAudioBroadcastProfile?.let { profile ->
                    merge(
                            // Register and start listen to onBroadcastMetadataChanged (means ready
                            // to add source)
                            // 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
@@ -122,9 +125,9 @@ constructor(
                        )
                        .mapNotNull { shouldListenToMetadata ->
                            if (shouldListenToMetadata) {
                                // onBroadcastMetadataChanged could emit multiple times during one
                                // audio sharing session, we only perform add source on the first
                                // time
                                // onBroadcastMetadataChanged could emit multiple times
                                // during one audio sharing session, we only perform add
                                // source on the first time
                                profile.onBroadcastMetadataChanged.firstOrNull()
                            } else {
                                null
+40 −3
Original line number Diff line number Diff line
@@ -20,18 +20,29 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.onServiceStateChanged
import com.android.settingslib.bluetooth.onSourceConnectedOrRemoved
import com.android.settingslib.volume.data.repository.AudioSharingRepository as SettingsLibAudioSharingRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext

interface AudioSharingRepository {
    val isAudioSharingProfilesReady: StateFlow<Boolean>

    val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?

    val audioSourceStateUpdate: Flow<Unit>
@@ -55,16 +66,40 @@ class AudioSharingRepositoryImpl(
    private val settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
    private val logger: BluetoothTileDialogLogger,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @Application private val coroutineScope: CoroutineScope,
) : AudioSharingRepository {

    override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast?
        get() = localBluetoothManager.profileManager?.leAudioBroadcastProfile
        get() = localBluetoothManager.profileManager.leAudioBroadcastProfile

    private val leAudioBroadcastAssistantProfile: LocalBluetoothLeBroadcastAssistant?
        get() = localBluetoothManager.profileManager?.leAudioBroadcastAssistantProfile
        get() = localBluetoothManager.profileManager.leAudioBroadcastAssistantProfile

    override val isAudioSharingProfilesReady: StateFlow<Boolean> =
        localBluetoothManager.profileManager.onServiceStateChanged
            .map {
                leAudioBroadcastProfile?.isProfileReady == true &&
                    leAudioBroadcastAssistantProfile?.isProfileReady == true
            }
            .onStart {
                emit(
                    leAudioBroadcastProfile?.isProfileReady == true &&
                        leAudioBroadcastAssistantProfile?.isProfileReady == true
                )
            }
            .flowOn(backgroundDispatcher)
            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)

    override val audioSourceStateUpdate: Flow<Unit> =
        isAudioSharingProfilesReady
            .flatMapLatest {
                if (it) {
                    leAudioBroadcastAssistantProfile?.onSourceConnectedOrRemoved ?: emptyFlow()
                } else {
                    emptyFlow()
                }
            }
            .flowOn(backgroundDispatcher)

    override val inAudioSharing: StateFlow<Boolean> =
        settingsLibAudioSharingRepository.inAudioSharing
@@ -124,6 +159,8 @@ class AudioSharingRepositoryImpl(

@SysUISingleton
class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
    override val isAudioSharingProfilesReady: StateFlow<Boolean> = MutableStateFlow(false)

    override val leAudioBroadcastProfile: LocalBluetoothLeBroadcast? = null

    override val audioSourceStateUpdate: Flow<Unit> = emptyFlow()
+4 −0
Original line number Diff line number Diff line
@@ -38,11 +38,13 @@ import com.android.systemui.bluetooth.qsdialog.DeviceItemFactory
import com.android.systemui.bluetooth.qsdialog.DeviceItemType
import com.android.systemui.bluetooth.qsdialog.SavedDeviceItemFactory
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Lazy
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope

/** Dagger module for audio sharing code for BT QS dialog */
@Module
@@ -56,6 +58,7 @@ interface AudioSharingModule {
            settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
            logger: BluetoothTileDialogLogger,
            @Background backgroundDispatcher: CoroutineDispatcher,
            @Application coroutineScope: CoroutineScope,
        ): AudioSharingRepository =
            if (
                (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
@@ -66,6 +69,7 @@ interface AudioSharingModule {
                    settingsLibAudioSharingRepository,
                    logger,
                    backgroundDispatcher,
                    coroutineScope,
                )
            } else {
                AudioSharingRepositoryEmptyImpl()
Loading