Improve volume sliders behaviour:
- Add stream mute/unmute when user taps the icon
- Snap sliders to the steps mapped to the corresponding values
- Fix AudioRepository mute/unmute parameters order
Flag: aconfig new_volume_panel TEAMFOOD
Test: manual on the phone
Test: atest AudioRepositoryTest
Test: atest AudioVolumeInteractorTest
Test: atest VolumeSliderInteractorTest
Fixes: 327586835
Fixes: 327596143
Change-Id: I887d97551cd9027af355fd0ebc064b52fe295f6b
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 0df4615..21cc9a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -161,11 +161,11 @@
override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) =
withContext(backgroundCoroutineContext) {
- if (isMuted) {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_MUTE)
- } else {
- audioManager.adjustStreamVolume(audioStream.value, 0, AudioManager.ADJUST_UNMUTE)
- }
+ audioManager.adjustStreamVolume(
+ audioStream.value,
+ if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE,
+ 0,
+ )
}
private fun getMinVolume(stream: AudioStream): Int =
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
index 1586b8f..c9ac97d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt
@@ -84,10 +84,10 @@
(audioStreamModel.audioStream.value == AudioManager.STREAM_NOTIFICATION &&
audioStreamModel.isMuted)
) {
- return 0
+ return audioStreamModel.minVolume
}
} else if (audioStreamModel.isMuted) {
- return 0
+ return audioStreamModel.minVolume
}
return audioStreamModel.volume
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
index 1728a80..9860cd8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt
@@ -77,13 +77,13 @@
`when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME)
`when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL)
`when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then {
- val streamType = it.arguments[1] as Int
- volumeByStream[it.arguments[0] as Int] = streamType
+ val streamType = it.arguments[0] as Int
+ volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int
triggerEvent(AudioManagerEvent.StreamVolumeChanged(AudioStream(streamType)))
}
`when`(audioManager.adjustStreamVolume(anyInt(), anyInt(), anyInt())).then {
val streamType = it.arguments[0] as Int
- isMuteByStream[streamType] = it.arguments[2] == AudioManager.ADJUST_MUTE
+ isMuteByStream[streamType] = it.arguments[1] == AudioManager.ADJUST_MUTE
triggerEvent(AudioManagerEvent.StreamMuteChanged(AudioStream(streamType)))
}
`when`(audioManager.getStreamVolume(anyInt())).thenAnswer {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 81d2da0..a787c50 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -76,7 +76,10 @@
VolumeSlider(
modifier = Modifier.weight(1f),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
@@ -114,9 +117,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth().padding(top = 16.dp),
state = sliderState,
- onValueChangeFinished = {
- sliderViewModel.onValueChangeFinished(sliderState, it)
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index 910ee72..b284c69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -43,7 +43,10 @@
VolumeSlider(
modifier = Modifier.fillMaxWidth(),
state = sliderState,
- onValueChangeFinished = { sliderViewModel.onValueChangeFinished(sliderState, it) },
+ onValueChange = { newValue: Float ->
+ sliderViewModel.onValueChanged(sliderState, newValue)
+ },
+ onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 3e0aee5..f9a712b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -18,19 +18,21 @@
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
-import androidx.compose.animation.expandVertically
-import androidx.compose.animation.shrinkVertically
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.IconButtonColors
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformSlider
import com.android.compose.PlatformSliderColors
@@ -40,23 +42,37 @@
@Composable
fun VolumeSlider(
state: SliderState,
- onValueChangeFinished: (Float) -> Unit,
+ onValueChange: (newValue: Float) -> Unit,
+ onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
) {
- var value by remember(state.value) { mutableFloatStateOf(state.value) }
+ val value by
+ animateFloatAsState(targetValue = state.value, label = "VolumeSliderValueAnimation")
PlatformSlider(
modifier = modifier,
value = value,
valueRange = state.valueRange,
- onValueChange = { value = it },
- onValueChangeFinished = { onValueChangeFinished(value) },
+ onValueChange = onValueChange,
enabled = state.isEnabled,
icon = { isDragging ->
if (isDragging) {
- Text(text = value.toInt().toString())
+ Text(text = value.toInt().toString(), color = LocalContentColor.current)
} else {
- state.icon?.let { Icon(modifier = Modifier.size(24.dp), icon = it) }
+ state.icon?.let {
+ IconButton(
+ onClick = onIconTapped,
+ colors =
+ IconButtonColors(
+ contentColor = LocalContentColor.current,
+ containerColor = Color.Transparent,
+ disabledContentColor = LocalContentColor.current,
+ disabledContainerColor = Color.Transparent,
+ )
+ ) {
+ Icon(modifier = Modifier.size(24.dp), icon = it)
+ }
+ }
}
},
colors = sliderColors,
@@ -66,19 +82,21 @@
modifier = Modifier.basicMarquee(),
text = state.label,
style = MaterialTheme.typography.titleMedium,
+ color = LocalContentColor.current,
maxLines = 1,
)
state.disabledMessage?.let { message ->
AnimatedVisibility(
!state.isEnabled,
- enter = expandVertically { it },
- exit = shrinkVertically { it },
+ enter = slideInVertically { it },
+ exit = slideOutVertically { it },
) {
Text(
modifier = Modifier.basicMarquee(),
text = message,
style = MaterialTheme.typography.bodySmall,
+ color = LocalContentColor.current,
maxLines = 1,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
index e06efe8..3d93654 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt
@@ -135,7 +135,7 @@
}
@Test
- fun streamIsMuted_getStream_volumeZero() {
+ fun streamIsMuted_getStream_volumeMin() {
with(kosmos) {
testScope.runTest {
val model by collectLastValue(underTest.getAudioStream(audioStream))
@@ -166,7 +166,7 @@
}
@Test
- fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsZero() {
+ fun ringerModeVibrateAndMuted_getNotificationStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
@@ -184,7 +184,7 @@
}
@Test
- fun ringerModeVibrate_getRingerStream_volumeIsZero() {
+ fun ringerModeVibrate_getRingerStream_volumeIsMin() {
with(kosmos) {
testScope.runTest {
audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_VIBRATE))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
index a1e4fca..681c839 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractorTest.kt
@@ -36,22 +36,12 @@
@Test
fun processVolumeToValue_muted_zero() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, null, true)).isEqualTo(0)
+ assertThat(underTest.processVolumeToValue(3, volumeRange, true)).isEqualTo(0)
}
@Test
- fun processVolumeToValue_currentValue_currentValue() {
- assertThat(underTest.processVolumeToValue(3, volumeRange, 30f, false)).isEqualTo(30f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 60f, false)).isEqualTo(10f)
- }
-
- @Test
- fun processVolumeToValue_currentValueDiffersNotEnoughVolume_returnsTranslatedVolume() {
- assertThat(underTest.processVolumeToValue(1, volumeRange, 12f, false)).isEqualTo(12f)
+ fun processVolumeToValue_returnsTranslatedVolume() {
+ assertThat(underTest.processVolumeToValue(2, volumeRange, false)).isEqualTo(20f)
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
index 0c91bbf..e37a8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/VolumeSliderInteractor.kt
@@ -37,24 +37,18 @@
fun processVolumeToValue(
volume: Int,
volumeRange: ClosedRange<Int>,
- currentValue: Float?,
isMuted: Boolean,
): Float {
if (isMuted) {
return 0f
}
- val changedVolume: Int? = currentValue?.let { translateValueToVolume(it, volumeRange) }
- return if (volume != volumeRange.start && volume == changedVolume) {
- currentValue
- } else {
- translateToRange(
- currentValue = volume.toFloat(),
- currentRangeStart = volumeRange.start.toFloat(),
- currentRangeEnd = volumeRange.endInclusive.toFloat(),
- targetRangeStart = displayValueRange.start,
- targetRangeEnd = displayValueRange.endInclusive,
- )
- }
+ return translateToRange(
+ currentValue = volume.toFloat(),
+ currentRangeStart = volumeRange.start.toFloat(),
+ currentRangeEnd = volumeRange.endInclusive.toFloat(),
+ targetRangeStart = displayValueRange.start,
+ targetRangeEnd = displayValueRange.endInclusive,
+ )
}
/** Translates [value] from [displayValueRange] to volume that has [volumeRange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 532e517..74a8a27 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -71,22 +71,20 @@
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
- private var value = 0f
override val slider: StateFlow<SliderState> =
combine(
audioVolumeInteractor.getAudioStream(audioStream),
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
) { model, isEnabled, ringerMode ->
- model.toState(value, isEnabled, ringerMode)
+ model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, EmptyState)
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
audioViewModel ?: return
coroutineScope.launch {
- value = newValue
val volume =
volumeSliderInteractor.translateValueToVolume(
newValue,
@@ -96,19 +94,20 @@
}
}
+ override fun toggleMuted(state: SliderState) {
+ val audioViewModel = state as? State
+ audioViewModel ?: return
+ coroutineScope.launch {
+ audioVolumeInteractor.setMuted(audioStream, !audioViewModel.audioStreamModel.isMuted)
+ }
+ }
+
private fun AudioStreamModel.toState(
- value: Float,
isEnabled: Boolean,
ringerMode: RingerMode,
): State {
return State(
- value =
- volumeSliderInteractor.processVolumeToValue(
- volume,
- volumeRange,
- value,
- isMuted,
- ),
+ value = volumeSliderInteractor.processVolumeToValue(volume, volumeRange, isMuted),
valueRange = volumeSliderInteractor.displayValueRange,
icon = getIcon(ringerMode),
label = labelsByStream[audioStream]?.let(context::getString)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index ae93826..efee100 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -27,7 +27,6 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -46,17 +45,13 @@
) : SliderViewModel {
private val volumeRange = 0..routingSession.routingSessionInfo.volumeMax
- private val value = MutableStateFlow(0f)
override val slider: StateFlow<SliderState> =
- combine(value, mediaOutputInteractor.currentConnectedDevice) { value, _ ->
- getCurrentState(value)
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState(value.value))
+ combine(mediaOutputInteractor.currentConnectedDevice) { _ -> getCurrentState() }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, getCurrentState())
- override fun onValueChangeFinished(state: SliderState, newValue: Float) {
+ override fun onValueChanged(state: SliderState, newValue: Float) {
coroutineScope.launch {
- value.value = newValue
castVolumeInteractor.setVolume(
routingSession,
volumeSliderInteractor.translateValueToVolume(newValue, volumeRange),
@@ -64,13 +59,16 @@
}
}
- private fun getCurrentState(value: Float): State {
- return State(
+ override fun toggleMuted(state: SliderState) {
+ // do nothing because this action isn't supported for Cast sliders.
+ }
+
+ private fun getCurrentState(): State =
+ State(
value =
volumeSliderInteractor.processVolumeToValue(
volume = routingSession.routingSessionInfo.volume,
volumeRange = volumeRange,
- currentValue = value,
isMuted = false,
),
valueRange = volumeSliderInteractor.displayValueRange,
@@ -78,7 +76,6 @@
label = context.getString(R.string.media_device_cast),
isEnabled = true,
)
- }
private data class State(
override val value: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 0c4b322..74aee55 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -23,5 +23,7 @@
val slider: StateFlow<SliderState>
- fun onValueChangeFinished(state: SliderState, newValue: Float)
+ fun onValueChanged(state: SliderState, newValue: Float)
+
+ fun toggleMuted(state: SliderState)
}