1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor 18 19 import android.media.session.MediaController 20 import android.media.session.PlaybackState 21 import com.android.settingslib.volume.data.repository.MediaControllerRepository 22 import com.android.systemui.dagger.qualifiers.Background 23 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel 24 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession 25 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope 26 import javax.inject.Inject 27 import kotlin.coroutines.CoroutineContext 28 import kotlinx.coroutines.ExperimentalCoroutinesApi 29 import kotlinx.coroutines.flow.Flow 30 import kotlinx.coroutines.flow.FlowCollector 31 import kotlinx.coroutines.flow.filterIsInstance 32 import kotlinx.coroutines.flow.flatMapLatest 33 import kotlinx.coroutines.flow.flowOf 34 import kotlinx.coroutines.flow.flowOn 35 import kotlinx.coroutines.flow.map 36 import kotlinx.coroutines.flow.onStart 37 import kotlinx.coroutines.withContext 38 39 /** Allows to observe and change [MediaDeviceSession] state. */ 40 @OptIn(ExperimentalCoroutinesApi::class) 41 @VolumePanelScope 42 class MediaDeviceSessionInteractor 43 @Inject 44 constructor( 45 @Background private val backgroundCoroutineContext: CoroutineContext, 46 private val mediaControllerInteractor: MediaControllerInteractor, 47 private val mediaControllerRepository: MediaControllerRepository, 48 ) { 49 50 /** [PlaybackState] changes for the [MediaDeviceSession]. */ 51 fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> { 52 return stateChanges(session) { 53 emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState)) 54 } 55 .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class) 56 .map { it.state } 57 } 58 59 /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */ 60 fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo> { 61 return stateChanges(session) { 62 emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo)) 63 } 64 .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class) 65 .map { it.info } 66 } 67 68 private fun stateChanges( 69 session: MediaDeviceSession, 70 onStart: 71 suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit, 72 ): Flow<MediaControllerChangeModel?> = 73 mediaControllerRepository.activeSessions 74 .flatMapLatest { controllers -> 75 val controller: MediaController = 76 findControllerForSession(controllers, session) 77 ?: return@flatMapLatest flowOf(null) 78 mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) } 79 } 80 .flowOn(backgroundCoroutineContext) 81 82 /** Set [MediaDeviceSession] volume to [volume]. */ 83 suspend fun setSessionVolume(mediaDeviceSession: MediaDeviceSession, volume: Int): Boolean { 84 if (!mediaDeviceSession.canAdjustVolume) { 85 return false 86 } 87 return withContext(backgroundCoroutineContext) { 88 val controller = 89 findControllerForSession( 90 mediaControllerRepository.activeSessions.value, 91 mediaDeviceSession, 92 ) 93 if (controller == null) { 94 false 95 } else { 96 controller.setVolumeTo(volume, 0) 97 true 98 } 99 } 100 } 101 102 private fun findControllerForSession( 103 controllers: Collection<MediaController>, 104 mediaDeviceSession: MediaDeviceSession, 105 ): MediaController? = 106 controllers.firstOrNull { it.sessionToken == mediaDeviceSession.sessionToken } 107 } 108