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