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.volume.ui.viewmodel
18 
19 import com.android.settingslib.volume.shared.model.AudioStream
20 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
21 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
22 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
23 import com.android.systemui.volume.panel.component.volume.domain.interactor.AudioSlidersInteractor
24 import com.android.systemui.volume.panel.component.volume.domain.model.SliderType
25 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
26 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
27 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
28 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
29 import com.android.systemui.volume.panel.shared.model.filterData
30 import javax.inject.Inject
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 import kotlinx.coroutines.coroutineScope
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.MutableStateFlow
36 import kotlinx.coroutines.flow.SharingStarted
37 import kotlinx.coroutines.flow.StateFlow
38 import kotlinx.coroutines.flow.filterNotNull
39 import kotlinx.coroutines.flow.flatMapLatest
40 import kotlinx.coroutines.flow.flowOf
41 import kotlinx.coroutines.flow.map
42 import kotlinx.coroutines.flow.onEach
43 import kotlinx.coroutines.flow.stateIn
44 import kotlinx.coroutines.flow.transformLatest
45 import kotlinx.coroutines.launch
46 
47 /**
48  * Controls the behaviour of the whole audio
49  * [com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent].
50  */
51 @OptIn(ExperimentalCoroutinesApi::class)
52 @VolumePanelScope
53 class AudioVolumeComponentViewModel
54 @Inject
55 constructor(
56     @VolumePanelScope private val scope: CoroutineScope,
57     mediaOutputInteractor: MediaOutputInteractor,
58     mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
59     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
60     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
61     streamsInteractor: AudioSlidersInteractor,
62 ) {
63 
64     private val mutableIsExpanded = MutableStateFlow<Boolean?>(null)
65     private val isPlaybackActive: Flow<Boolean?> =
66         mediaOutputInteractor.defaultActiveMediaSession
67             .filterData()
68             .flatMapLatest { session ->
69                 if (session == null) {
70                     flowOf(false)
71                 } else {
72                     mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true }
73                 }
74             }
75             .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive }
76             .stateIn(scope, SharingStarted.Eagerly, null)
77     private val portraitExpandable: Flow<SlidersExpandableViewModel> =
78         isPlaybackActive
79             .filterNotNull()
80             .flatMapLatest { isActive ->
81                 if (isActive) {
82                     mutableIsExpanded.filterNotNull().map { isExpanded ->
83                         SlidersExpandableViewModel.Expandable(isExpanded)
84                     }
85                 } else {
86                     flowOf(SlidersExpandableViewModel.Fixed)
87                 }
88             }
89             .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable)
90 
91     val sliderViewModels: StateFlow<List<SliderViewModel>> =
92         streamsInteractor.volumePanelSliders
93             .transformLatest { sliderTypes ->
94                 coroutineScope {
95                     val viewModels =
96                         sliderTypes.map { type ->
97                             when (type) {
98                                 is SliderType.Stream -> createStreamViewModel(type.stream)
99                                 is SliderType.MediaDeviceCast ->
100                                     createSessionViewModel(type.session)
101                             }
102                         }
103                     emit(viewModels)
104                 }
105             }
106             .stateIn(scope, SharingStarted.Eagerly, emptyList())
107 
108     fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> {
109         return if (isPortrait) {
110             portraitExpandable
111         } else {
112             flowOf(SlidersExpandableViewModel.Fixed)
113         }
114     }
115 
116     fun onExpandedChanged(isExpanded: Boolean) {
117         scope.launch { mutableIsExpanded.value = isExpanded }
118     }
119 
120     private fun CoroutineScope.createSessionViewModel(
121         session: MediaDeviceSession
122     ): CastVolumeSliderViewModel {
123         return castVolumeSliderViewModelFactory.create(session, this)
124     }
125 
126     private fun CoroutineScope.createStreamViewModel(
127         stream: AudioStream,
128     ): AudioStreamSliderViewModel {
129         return streamSliderViewModelFactory.create(
130             AudioStreamSliderViewModel.FactoryAudioStreamWrapper(stream),
131             this,
132         )
133     }
134 }
135