1 /*
2  * 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.slider.ui.viewmodel
18 
19 import android.content.Context
20 import android.media.session.MediaController.PlaybackInfo
21 import com.android.systemui.common.shared.model.Icon
22 import com.android.systemui.res.R
23 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
24 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
25 import dagger.assisted.Assisted
26 import dagger.assisted.AssistedFactory
27 import dagger.assisted.AssistedInject
28 import kotlin.math.roundToInt
29 import kotlinx.coroutines.CoroutineScope
30 import kotlinx.coroutines.flow.SharingStarted
31 import kotlinx.coroutines.flow.StateFlow
32 import kotlinx.coroutines.flow.mapNotNull
33 import kotlinx.coroutines.flow.stateIn
34 import kotlinx.coroutines.launch
35 
36 class CastVolumeSliderViewModel
37 @AssistedInject
38 constructor(
39     @Assisted private val session: MediaDeviceSession,
40     @Assisted private val coroutineScope: CoroutineScope,
41     private val context: Context,
42     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
43 ) : SliderViewModel {
44 
45     override val slider: StateFlow<SliderState> =
46         mediaDeviceSessionInteractor
47             .playbackInfo(session)
<lambda>null48             .mapNotNull { it?.getCurrentState() }
49             .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
50 
onValueChangednull51     override fun onValueChanged(state: SliderState, newValue: Float) {
52         coroutineScope.launch {
53             mediaDeviceSessionInteractor.setSessionVolume(session, newValue.roundToInt())
54         }
55     }
56 
onValueChangeFinishednull57     override fun onValueChangeFinished() {}
58 
toggleMutednull59     override fun toggleMuted(state: SliderState) {
60         // do nothing because this action isn't supported for Cast sliders.
61     }
62 
PlaybackInfonull63     private fun PlaybackInfo.getCurrentState(): State {
64         val volumeRange = 0..maxVolume
65         return State(
66             value = currentVolume.toFloat(),
67             valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(),
68             icon = Icon.Resource(R.drawable.ic_cast, null),
69             label = context.getString(R.string.media_device_cast),
70             isEnabled = true,
71             a11yStep = 1,
72         )
73     }
74 
75     private data class State(
76         override val value: Float,
77         override val valueRange: ClosedFloatingPointRange<Float>,
78         override val icon: Icon,
79         override val label: String,
80         override val isEnabled: Boolean,
81         override val a11yStep: Int,
82     ) : SliderState {
83         override val disabledMessage: String?
84             get() = null
85 
86         override val isMutable: Boolean
87             get() = false
88 
89         override val a11yClickDescription: String?
90             get() = null
91 
92         override val a11yStateDescription: String?
93             get() = null
94     }
95 
96     @AssistedFactory
97     interface Factory {
98 
createnull99         fun create(
100             session: MediaDeviceSession,
101             coroutineScope: CoroutineScope,
102         ): CastVolumeSliderViewModel
103     }
104 }
105