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