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