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

Commit 9ad55393 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Add MediaControllerInteractor to fake Looper usage in tests" into main

parents 650c271a 0b91e28c
Loading
Loading
Loading
Loading
+2 −8
Original line number Diff line number Diff line
@@ -16,17 +16,16 @@

package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor

import android.os.Handler
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.panel.shared.model.filterData
import com.android.systemui.volume.remoteMediaController
@@ -55,12 +54,7 @@ class MediaDeviceSessionInteractorTest : SysuiTestCase() {
                listOf(localMediaController, remoteMediaController)
            )

            underTest =
                MediaDeviceSessionInteractor(
                    testScope.testScheduler,
                    Handler(TestableLooper.get(kosmos.testCase).looper),
                    mediaControllerRepository,
                )
            underTest = mediaDeviceSessionInteractor
        }
    }

+7 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractorImpl
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -41,6 +43,11 @@ interface MediaDevicesModule {
        impl: LocalMediaRepositoryFactoryImpl
    ): LocalMediaRepositoryFactory

    @Binds
    fun bindMediaControllerInteractor(
        impl: MediaControllerInteractorImpl
    ): MediaControllerInteractor

    companion object {

        @Provides
+34 −38
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 */

package com.android.settingslib.volume.data.repository
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor

import android.media.MediaMetadata
import android.media.session.MediaController
@@ -22,79 +22,75 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.os.Handler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import javax.inject.Inject
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.launch

interface MediaControllerInteractor {

    /** [MediaController.Callback] flow representation. */
fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> {
    return callbackFlow {
        val callback = MediaControllerCallbackProducer(this)
        registerCallback(callback, handler)
        awaitClose { unregisterCallback(callback) }
    fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel>
}
}

/** Models particular change event received by [MediaController.Callback]. */
sealed interface MediaControllerChange {

    data object SessionDestroyed : MediaControllerChange

    data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange

    data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange

    data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange
@SysUISingleton
class MediaControllerInteractorImpl
@Inject
constructor(
    @Background private val backgroundHandler: Handler,
) : MediaControllerInteractor {

    data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
        MediaControllerChange

    data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange

    data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange

    data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange
    override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
        return conflatedCallbackFlow {
            val callback = MediaControllerCallbackProducer(this)
            mediaController.registerCallback(callback, backgroundHandler)
            awaitClose { mediaController.unregisterCallback(callback) }
        }
    }
}

private class MediaControllerCallbackProducer(
    private val producingScope: ProducerScope<MediaControllerChange>
    private val producingScope: ProducerScope<MediaControllerChangeModel>
) : MediaController.Callback() {

    override fun onSessionDestroyed() {
        send(MediaControllerChange.SessionDestroyed)
        send(MediaControllerChangeModel.SessionDestroyed)
    }

    override fun onSessionEvent(event: String, extras: Bundle?) {
        send(MediaControllerChange.SessionEvent(event, extras))
        send(MediaControllerChangeModel.SessionEvent(event, extras))
    }

    override fun onPlaybackStateChanged(state: PlaybackState?) {
        send(MediaControllerChange.PlaybackStateChanged(state))
        send(MediaControllerChangeModel.PlaybackStateChanged(state))
    }

    override fun onMetadataChanged(metadata: MediaMetadata?) {
        send(MediaControllerChange.MetadataChanged(metadata))
        send(MediaControllerChangeModel.MetadataChanged(metadata))
    }

    override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
        send(MediaControllerChange.QueueChanged(queue))
        send(MediaControllerChangeModel.QueueChanged(queue))
    }

    override fun onQueueTitleChanged(title: CharSequence?) {
        send(MediaControllerChange.QueueTitleChanged(title))
        send(MediaControllerChangeModel.QueueTitleChanged(title))
    }

    override fun onExtrasChanged(extras: Bundle?) {
        send(MediaControllerChange.ExtrasChanged(extras))
        send(MediaControllerChangeModel.ExtrasChanged(extras))
    }

    override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
        send(MediaControllerChange.AudioInfoChanged(info))
        send(MediaControllerChangeModel.AudioInfoChanged(info))
    }

    private fun send(change: MediaControllerChange) {
    private fun send(change: MediaControllerChangeModel) {
        producingScope.launch { producingScope.send(change) }
    }
}
+10 −11
Original line number Diff line number Diff line
@@ -18,11 +18,9 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto

import android.media.session.MediaController
import android.media.session.PlaybackState
import android.os.Handler
import com.android.settingslib.volume.data.repository.MediaControllerChange
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -45,38 +43,39 @@ class MediaDeviceSessionInteractor
@Inject
constructor(
    @Background private val backgroundCoroutineContext: CoroutineContext,
    @Background private val backgroundHandler: Handler,
    private val mediaControllerInteractor: MediaControllerInteractor,
    private val mediaControllerRepository: MediaControllerRepository,
) {

    /** [PlaybackState] changes for the [MediaDeviceSession]. */
    fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> {
        return stateChanges(session) {
                emit(MediaControllerChange.PlaybackStateChanged(it.playbackState))
                emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState))
            }
            .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class)
            .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class)
            .map { it.state }
    }

    /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */
    fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> {
        return stateChanges(session) {
                emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo))
                emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo))
            }
            .filterIsInstance(MediaControllerChange.AudioInfoChanged::class)
            .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class)
            .map { it.info }
    }

    private fun stateChanges(
        session: MediaDeviceSession,
        onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit,
    ): Flow<MediaControllerChange?> =
        onStart:
            suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit,
    ): Flow<MediaControllerChangeModel?> =
        mediaControllerRepository.activeSessions
            .flatMapLatest { controllers ->
                val controller: MediaController =
                    findControllerForSession(controllers, session)
                        ?: return@flatMapLatest flowOf(null)
                controller.stateChanges(backgroundHandler).onStart { onStart(controller) }
                mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) }
            }
            .flowOn(backgroundCoroutineContext)

+5 −4
Original line number Diff line number Diff line
@@ -19,12 +19,10 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto
import android.content.pm.PackageManager
import android.media.VolumeProvider
import android.media.session.MediaController
import android.os.Handler
import android.util.Log
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
@@ -61,7 +59,7 @@ constructor(
    @VolumePanelScope private val coroutineScope: CoroutineScope,
    @Background private val backgroundCoroutineContext: CoroutineContext,
    mediaControllerRepository: MediaControllerRepository,
    @Background private val backgroundHandler: Handler,
    private val mediaControllerInteractor: MediaControllerInteractor,
) {

    private val activeMediaControllers: Flow<MediaControllers> =
@@ -194,7 +192,10 @@ constructor(
            return flowOf(null)
        }

        return stateChanges(backgroundHandler).map { this }.onStart { emit(this@stateChanges) }
        return mediaControllerInteractor
            .stateChanges(this)
            .map { this }
            .onStart { emit(this@stateChanges) }
    }

    private data class MediaControllers(
Loading