diff options
9 files changed, 164 insertions, 65 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt index 64c9429cbe25..46df0c227f1c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt @@ -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 } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt index 155102c9b9a7..369610882959 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt @@ -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 diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt index 1f037c0280e3..4812765d4afe 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt @@ -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 -/** [MediaController.Callback] flow representation. */ -fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> { - return callbackFlow { - val callback = MediaControllerCallbackProducer(this) - registerCallback(callback, handler) - awaitClose { unregisterCallback(callback) } - } -} - -/** 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 +interface MediaControllerInteractor { - data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange - - data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : - MediaControllerChange - - data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange - - data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange + /** [MediaController.Callback] flow representation. */ + fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> +} - data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange +@SysUISingleton +class MediaControllerInteractorImpl +@Inject +constructor( + @Background private val backgroundHandler: Handler, +) : MediaControllerInteractor { + + 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) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index dc73344fafe6..599bd73abb69 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -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) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b00829e48404..9fbd79accf80 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt new file mode 100644 index 000000000000..ef5a44a7a2fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt @@ -0,0 +1,45 @@ +/* + * 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.mediaoutput.domain.model + +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.os.Bundle + +/** Models particular change event received by [MediaController.Callback]. */ +sealed interface MediaControllerChangeModel { + + data object SessionDestroyed : MediaControllerChangeModel + + data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChangeModel + + data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChangeModel + + data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChangeModel + + data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : + MediaControllerChangeModel + + data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChangeModel + + data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel + + data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : + MediaControllerChangeModel +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index 59bbff171725..1b58582a806f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -18,8 +18,6 @@ package com.android.systemui.volume import android.content.packageManager import android.content.pm.ApplicationInfo -import android.os.Handler -import android.os.looper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogManager @@ -32,6 +30,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.data.repository.F import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() } val Kosmos.localMediaRepositoryFactory by @@ -53,7 +52,7 @@ val Kosmos.mediaOutputInteractor by testScope.backgroundScope, testScope.testScheduler, mediaControllerRepository, - Handler(looper), + mediaControllerInteractor, ) } @@ -61,7 +60,7 @@ val Kosmos.mediaDeviceSessionInteractor by Kosmos.Fixture { MediaDeviceSessionInteractor( testScope.testScheduler, - Handler(looper), + mediaControllerInteractor, mediaControllerRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt new file mode 100644 index 000000000000..f03ec01bd36f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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.mediaoutput.domain.interactor + +import android.media.session.MediaController +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeMediaControllerInteractor : MediaControllerInteractor { + + private val stateChanges = MutableSharedFlow<MediaControllerChangeModel>(replay = 1) + + override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> = + stateChanges + + fun updateState(change: MediaControllerChangeModel) { + stateChanges.tryEmit(change) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt new file mode 100644 index 000000000000..652b3ea984e7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.mediaoutput.domain.interactor + +import android.os.Handler +import android.os.looper +import com.android.systemui.kosmos.Kosmos + +var Kosmos.mediaControllerInteractor: MediaControllerInteractor by + Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) } |