Loading packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +27 −7 Original line number Original line Diff line number Diff line Loading @@ -34,12 +34,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors import com.android.compose.PlatformSliderColors Loading @@ -60,14 +63,31 @@ fun VolumeSlider( PlatformSlider( PlatformSlider( modifier = modifier = modifier.clearAndSetSemantics { modifier.clearAndSetSemantics { if (!state.isEnabled) disabled() if (state.isEnabled) { contentDescription = state.label state.a11yClickDescription?.let { customActions = listOf( CustomAccessibilityAction( it, ) { onIconTapped() true } ) } state.a11yStateDescription?.let { stateDescription = it } ?: run { // provide a not animated value to the a11y because it fails to announce // the settled value when it changes rapidly. progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) } } else { disabled() contentDescription = contentDescription = state.disabledMessage?.let { "${state.label}, $it" } ?: state.label state.disabledMessage?.let { "${state.label}, $it" } ?: state.label // provide a not animated value to the a11y because it fails to announce the // settled value when it changes rapidly. if (state.isEnabled) { progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) } } setProgress { targetValue -> setProgress { targetValue -> val targetDirection = val targetDirection = Loading packages/SystemUI/res/values/strings.xml +8 −2 Original line number Original line Diff line number Diff line Loading @@ -1636,9 +1636,15 @@ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_mute">mute %s</string> <string name="volume_panel_hint_mute">Mute %s</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_unmute">unmute %s</string> <string name="volume_panel_hint_unmute">Unmute %s</string> <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_muted">muted</string> <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_vibrate">vibrate</string> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +42 −21 Original line number Original line Diff line number Diff line Loading @@ -49,6 +49,11 @@ constructor( private val uiEventLogger: UiEventLogger, private val uiEventLogger: UiEventLogger, ) : SliderViewModel { ) : SliderViewModel { private val streamsAffectedByRing = setOf( AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION, ) private val audioStream = audioStreamWrapper.audioStream private val audioStream = audioStreamWrapper.audioStream private val iconsByStream = private val iconsByStream = mapOf( mapOf( Loading Loading @@ -125,15 +130,42 @@ constructor( isEnabled: Boolean, isEnabled: Boolean, ringerMode: RingerMode, ringerMode: RingerMode, ): State { ): State { val label = labelsByStream[audioStream]?.let(context::getString) ?: error("No label for the stream: $audioStream") return State( return State( value = volume.toFloat(), value = volume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), icon = getIcon(ringerMode), icon = getIcon(ringerMode), label = labelsByStream[audioStream]?.let(context::getString) label = label, ?: error("No label for the stream: $audioStream"), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), isEnabled = isEnabled, isEnabled = isEnabled, a11yStep = volumeRange.step, a11yStep = volumeRange.step, a11yClickDescription = context.getString( if (isMuted) { R.string.volume_panel_hint_unmute } else { R.string.volume_panel_hint_mute }, label, ), a11yStateDescription = if (volume == volumeRange.first) { context.getString( if (audioStream.value in streamsAffectedByRing) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.string.volume_panel_hint_vibrate } else { R.string.volume_panel_hint_muted } } else { R.string.volume_panel_hint_muted } ) } else { null }, audioStreamModel = this, audioStreamModel = this, isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), ) ) Loading @@ -143,28 +175,15 @@ constructor( val isMutedOrNoVolume = isMuted || volume == minVolume val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = val iconRes = if (isMutedOrNoVolume) { if (isMutedOrNoVolume) { when (audioStream.value) { if (audioStream.value in streamsAffectedByRing) { AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off AudioManager.STREAM_RING -> if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.drawable.ic_volume_ringer_vibrate R.drawable.ic_volume_ringer_vibrate } else { } else { R.drawable.ic_volume_off R.drawable.ic_volume_off } } AudioManager.STREAM_NOTIFICATION -> if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.drawable.ic_volume_ringer_vibrate } else { } else { R.drawable.ic_volume_off R.drawable.ic_volume_off } } AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off else -> { Log.wtf(TAG, "No icon for the stream: $audioStream") R.drawable.ic_volume_off } } } else { } else { iconsByStream[audioStream] iconsByStream[audioStream] ?: run { ?: run { Loading @@ -186,6 +205,8 @@ constructor( override val disabledMessage: String?, override val disabledMessage: String?, override val isEnabled: Boolean, override val isEnabled: Boolean, override val a11yStep: Int, override val a11yStep: Int, override val a11yClickDescription: String?, override val a11yStateDescription: String?, override val isMutable: Boolean, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, val audioStreamModel: AudioStreamModel, ) : SliderState ) : SliderState Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +7 −1 Original line number Original line Diff line number Diff line Loading @@ -68,7 +68,7 @@ constructor( icon = Icon.Resource(R.drawable.ic_cast, null), icon = Icon.Resource(R.drawable.ic_cast, null), label = context.getString(R.string.media_device_cast), label = context.getString(R.string.media_device_cast), isEnabled = true, isEnabled = true, a11yStep = 1 a11yStep = 1, ) ) } } Loading @@ -85,6 +85,12 @@ constructor( override val isMutable: Boolean override val isMutable: Boolean get() = false get() = false override val a11yClickDescription: String? get() = null override val a11yStateDescription: String? get() = null } } @AssistedFactory @AssistedFactory Loading packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -34,6 +34,8 @@ sealed interface SliderState { * enough to trigger rounding to the correct value. * enough to trigger rounding to the correct value. */ */ val a11yStep: Int val a11yStep: Int val a11yClickDescription: String? val a11yStateDescription: String? val disabledMessage: String? val disabledMessage: String? val isMutable: Boolean val isMutable: Boolean Loading @@ -44,6 +46,8 @@ sealed interface SliderState { override val label: String = "" override val label: String = "" override val disabledMessage: String? = null override val disabledMessage: String? = null override val a11yStep: Int = 0 override val a11yStep: Int = 0 override val a11yClickDescription: String? = null override val a11yStateDescription: String? = null override val isEnabled: Boolean = true override val isEnabled: Boolean = true override val isMutable: Boolean = false override val isMutable: Boolean = false } } Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +27 −7 Original line number Original line Diff line number Diff line Loading @@ -34,12 +34,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.setProgress import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors import com.android.compose.PlatformSliderColors Loading @@ -60,14 +63,31 @@ fun VolumeSlider( PlatformSlider( PlatformSlider( modifier = modifier = modifier.clearAndSetSemantics { modifier.clearAndSetSemantics { if (!state.isEnabled) disabled() if (state.isEnabled) { contentDescription = state.label state.a11yClickDescription?.let { customActions = listOf( CustomAccessibilityAction( it, ) { onIconTapped() true } ) } state.a11yStateDescription?.let { stateDescription = it } ?: run { // provide a not animated value to the a11y because it fails to announce // the settled value when it changes rapidly. progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) } } else { disabled() contentDescription = contentDescription = state.disabledMessage?.let { "${state.label}, $it" } ?: state.label state.disabledMessage?.let { "${state.label}, $it" } ?: state.label // provide a not animated value to the a11y because it fails to announce the // settled value when it changes rapidly. if (state.isEnabled) { progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) } } setProgress { targetValue -> setProgress { targetValue -> val targetDirection = val targetDirection = Loading
packages/SystemUI/res/values/strings.xml +8 −2 Original line number Original line Diff line number Diff line Loading @@ -1636,9 +1636,15 @@ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_mute">mute %s</string> <string name="volume_panel_hint_mute">Mute %s</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_unmute">unmute %s</string> <string name="volume_panel_hint_unmute">Unmute %s</string> <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_muted">muted</string> <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] --> <string name="volume_panel_hint_vibrate">vibrate</string> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +42 −21 Original line number Original line Diff line number Diff line Loading @@ -49,6 +49,11 @@ constructor( private val uiEventLogger: UiEventLogger, private val uiEventLogger: UiEventLogger, ) : SliderViewModel { ) : SliderViewModel { private val streamsAffectedByRing = setOf( AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION, ) private val audioStream = audioStreamWrapper.audioStream private val audioStream = audioStreamWrapper.audioStream private val iconsByStream = private val iconsByStream = mapOf( mapOf( Loading Loading @@ -125,15 +130,42 @@ constructor( isEnabled: Boolean, isEnabled: Boolean, ringerMode: RingerMode, ringerMode: RingerMode, ): State { ): State { val label = labelsByStream[audioStream]?.let(context::getString) ?: error("No label for the stream: $audioStream") return State( return State( value = volume.toFloat(), value = volume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), icon = getIcon(ringerMode), icon = getIcon(ringerMode), label = labelsByStream[audioStream]?.let(context::getString) label = label, ?: error("No label for the stream: $audioStream"), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), isEnabled = isEnabled, isEnabled = isEnabled, a11yStep = volumeRange.step, a11yStep = volumeRange.step, a11yClickDescription = context.getString( if (isMuted) { R.string.volume_panel_hint_unmute } else { R.string.volume_panel_hint_mute }, label, ), a11yStateDescription = if (volume == volumeRange.first) { context.getString( if (audioStream.value in streamsAffectedByRing) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.string.volume_panel_hint_vibrate } else { R.string.volume_panel_hint_muted } } else { R.string.volume_panel_hint_muted } ) } else { null }, audioStreamModel = this, audioStreamModel = this, isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), ) ) Loading @@ -143,28 +175,15 @@ constructor( val isMutedOrNoVolume = isMuted || volume == minVolume val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = val iconRes = if (isMutedOrNoVolume) { if (isMutedOrNoVolume) { when (audioStream.value) { if (audioStream.value in streamsAffectedByRing) { AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off AudioManager.STREAM_RING -> if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.drawable.ic_volume_ringer_vibrate R.drawable.ic_volume_ringer_vibrate } else { } else { R.drawable.ic_volume_off R.drawable.ic_volume_off } } AudioManager.STREAM_NOTIFICATION -> if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { R.drawable.ic_volume_ringer_vibrate } else { } else { R.drawable.ic_volume_off R.drawable.ic_volume_off } } AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off else -> { Log.wtf(TAG, "No icon for the stream: $audioStream") R.drawable.ic_volume_off } } } else { } else { iconsByStream[audioStream] iconsByStream[audioStream] ?: run { ?: run { Loading @@ -186,6 +205,8 @@ constructor( override val disabledMessage: String?, override val disabledMessage: String?, override val isEnabled: Boolean, override val isEnabled: Boolean, override val a11yStep: Int, override val a11yStep: Int, override val a11yClickDescription: String?, override val a11yStateDescription: String?, override val isMutable: Boolean, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, val audioStreamModel: AudioStreamModel, ) : SliderState ) : SliderState Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +7 −1 Original line number Original line Diff line number Diff line Loading @@ -68,7 +68,7 @@ constructor( icon = Icon.Resource(R.drawable.ic_cast, null), icon = Icon.Resource(R.drawable.ic_cast, null), label = context.getString(R.string.media_device_cast), label = context.getString(R.string.media_device_cast), isEnabled = true, isEnabled = true, a11yStep = 1 a11yStep = 1, ) ) } } Loading @@ -85,6 +85,12 @@ constructor( override val isMutable: Boolean override val isMutable: Boolean get() = false get() = false override val a11yClickDescription: String? get() = null override val a11yStateDescription: String? get() = null } } @AssistedFactory @AssistedFactory Loading
packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +4 −0 Original line number Original line Diff line number Diff line Loading @@ -34,6 +34,8 @@ sealed interface SliderState { * enough to trigger rounding to the correct value. * enough to trigger rounding to the correct value. */ */ val a11yStep: Int val a11yStep: Int val a11yClickDescription: String? val a11yStateDescription: String? val disabledMessage: String? val disabledMessage: String? val isMutable: Boolean val isMutable: Boolean Loading @@ -44,6 +46,8 @@ sealed interface SliderState { override val label: String = "" override val label: String = "" override val disabledMessage: String? = null override val disabledMessage: String? = null override val a11yStep: Int = 0 override val a11yStep: Int = 0 override val a11yClickDescription: String? = null override val a11yStateDescription: String? = null override val isEnabled: Boolean = true override val isEnabled: Boolean = true override val isMutable: Boolean = false override val isMutable: Boolean = false } } Loading