Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad58b2d2 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Making haptics in new volume dialog slider continuous.

Given the short length of the slider, dragging through its many discrete
steps makes it feel like the slider is actually continuous. Therefore,
we make sure we deliver continous haptics on the vertical volume slider.
In the case of the horizontal sliders in the volume panel, the surface
area is large enough so that the discrete steps are salient in the
dragging motion. Thus, we keep these haptics discrete.

We achieve these behaviors by providing different bundles of
SliderHapticFeedbackConfig and SeekableSliderTrackerConfig objects in a
new VolumeHapticsConfigs data class. Discretized configs are supplied to
the volume sliders in the volume panel, and continuous configs are given
to the vertical volume dialog.

Test: manual. Verified that the volume dialog delivers continuous
  haptics and the volume panel delivers discrete haptics.
Flag: EXEMPT bugfix
Bug: 407025177

Change-Id: I6e9d16f04060af68081f4728a018c862c34dce19
parent a453f06d
Loading
Loading
Loading
Loading
+11 −6
Original line number Original line Diff line number Diff line
@@ -192,7 +192,11 @@ fun VolumeSlider(
                        hapticsViewModelFactory?.let {
                        hapticsViewModelFactory?.let {
                            Haptics.Enabled(
                            Haptics.Enabled(
                                hapticsViewModelFactory = it,
                                hapticsViewModelFactory = it,
                                hapticFilter = state.hapticFilter,
                                hapticConfigs =
                                    VolumeHapticsConfigsProvider.discreteConfigs(
                                        state.valueRange.stepSize(),
                                        state.hapticFilter,
                                    ),
                                orientation = Orientation.Horizontal,
                                orientation = Orientation.Horizontal,
                            )
                            )
                        } ?: Haptics.Disabled,
                        } ?: Haptics.Disabled,
@@ -372,16 +376,15 @@ private fun setUpHapticsViewModel(
    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
): SliderHapticsViewModel? {
): SliderHapticsViewModel? {
    return hapticsViewModelFactory?.let {
    return hapticsViewModelFactory?.let {
        val configs =
            VolumeHapticsConfigsProvider.discreteConfigs(valueRange.stepSize(), hapticFilter)
        rememberViewModel(traceName = "SliderHapticsViewModel") {
        rememberViewModel(traceName = "SliderHapticsViewModel") {
                it.create(
                it.create(
                    interactionSource,
                    interactionSource,
                    valueRange,
                    valueRange,
                    Orientation.Horizontal,
                    Orientation.Horizontal,
                    VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(
                    configs.hapticFeedbackConfig,
                        valueRange,
                    configs.sliderTrackerConfig,
                        hapticFilter,
                    ),
                    VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
                )
                )
            }
            }
            .also { hapticsViewModel ->
            .also { hapticsViewModel ->
@@ -399,3 +402,5 @@ private fun setUpHapticsViewModel(
            }
            }
    }
    }
}
}

private fun ClosedFloatingPointRange<Float>.stepSize(): Float = 1f / (endInclusive - start)
+5 −1
Original line number Original line Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.compose.SliderTrack
import com.android.systemui.volume.dialog.sliders.ui.compose.SliderTrack
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogOverscrollViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import com.android.systemui.volume.ui.compose.slider.AccessibilityParams
import com.android.systemui.volume.ui.compose.slider.AccessibilityParams
import com.android.systemui.volume.ui.compose.slider.Haptics
import com.android.systemui.volume.ui.compose.slider.Haptics
import com.android.systemui.volume.ui.compose.slider.Slider
import com.android.systemui.volume.ui.compose.slider.Slider
@@ -130,7 +131,10 @@ private fun VolumeDialogSlider(
            hapticsViewModelFactory?.let {
            hapticsViewModelFactory?.let {
                Haptics.Enabled(
                Haptics.Enabled(
                    hapticsViewModelFactory = it,
                    hapticsViewModelFactory = it,
                    hapticFilter = SliderHapticFeedbackFilter(),
                    hapticConfigs =
                        VolumeHapticsConfigsProvider.continuousConfigs(
                            SliderHapticFeedbackFilter()
                        ),
                    orientation = Orientation.Vertical,
                    orientation = Orientation.Vertical,
                )
                )
            } ?: Haptics.Disabled,
            } ?: Haptics.Disabled,
+50 −18
Original line number Original line Diff line number Diff line
@@ -22,23 +22,55 @@ import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter


object VolumeHapticsConfigsProvider {
object VolumeHapticsConfigsProvider {


    fun sliderHapticFeedbackConfig(
    fun discreteConfigs(stepSize: Float, filter: SliderHapticFeedbackFilter): VolumeHapticsConfigs =
        valueRange: ClosedFloatingPointRange<Float>,
        provideConfigs(stepSize, filter)
        filter: SliderHapticFeedbackFilter = SliderHapticFeedbackFilter(),

    ): SliderHapticFeedbackConfig {
    fun continuousConfigs(filter: SliderHapticFeedbackFilter): VolumeHapticsConfigs =
        val sliderStepSize = 1f / (valueRange.endInclusive - valueRange.start)
        provideConfigs(stepSize = 0f, filter)
        return SliderHapticFeedbackConfig(

    private fun provideConfigs(
        stepSize: Float,
        filter: SliderHapticFeedbackFilter,
    ): VolumeHapticsConfigs {
        val hapticFeedbackConfig: SliderHapticFeedbackConfig
        val trackerConfig: SeekableSliderTrackerConfig
        if (stepSize == 0f) {
            // Create a set of continuous configs
            hapticFeedbackConfig =
                SliderHapticFeedbackConfig(
                    additionalVelocityMaxBump = 0.1f,
                    deltaProgressForDragThreshold = 0.05f,
                    numberOfLowTicks = 4,
                    maxVelocityToScale = 0.5f, /* slider progress(from 0 to 1) per sec */
                    filter = filter,
                )
            trackerConfig =
                SeekableSliderTrackerConfig(
                    lowerBookendThreshold = 0.01f,
                    upperBookendThreshold = 0.99f,
                )
        } else {
            // Create a set of discrete configs
            hapticFeedbackConfig =
                SliderHapticFeedbackConfig(
                    lowerBookendScale = 0.2f,
                    lowerBookendScale = 0.2f,
                    progressBasedDragMinScale = 0.2f,
                    progressBasedDragMinScale = 0.2f,
                    progressBasedDragMaxScale = 0.5f,
                    progressBasedDragMaxScale = 0.5f,
                    deltaProgressForDragThreshold = 0f,
                    deltaProgressForDragThreshold = 0f,
                    additionalVelocityMaxBump = 0.2f,
                    additionalVelocityMaxBump = 0.2f,
                    maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */
                    maxVelocityToScale = 0.1f, /* slider progress(from 0 to 1) per sec */
            sliderStepSize = sliderStepSize,
                    sliderStepSize = stepSize,
                    filter = filter,
                    filter = filter,
                )
                )
    }
            trackerConfig =

    val seekableSliderTrackerConfig =
                SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f)
                SeekableSliderTrackerConfig(lowerBookendThreshold = 0f, upperBookendThreshold = 1f)
        }
        }
        return VolumeHapticsConfigs(hapticFeedbackConfig, trackerConfig)
    }
}

// A collection of configuration parameters for the haptics in the slider
data class VolumeHapticsConfigs(
    val hapticFeedbackConfig: SliderHapticFeedbackConfig,
    val sliderTrackerConfig: SeekableSliderTrackerConfig,
)
+15 −21
Original line number Original line Diff line number Diff line
@@ -36,7 +36,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.ProgressBarRangeInfo
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
@@ -46,14 +45,10 @@ 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.semantics.stateDescription
import com.android.systemui.haptics.slider.SliderHapticFeedbackFilter
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigsProvider
import com.android.systemui.volume.haptics.ui.VolumeHapticsConfigs
import kotlin.math.round
import kotlin.math.round
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map


@Composable
@Composable
fun Slider(
fun Slider(
@@ -95,7 +90,7 @@ fun Slider(
    val sliderState =
    val sliderState =
        remember(valueRange) { SliderState(value = animatedValue, valueRange = valueRange) }
        remember(valueRange) { SliderState(value = animatedValue, valueRange = valueRange) }
    val valueChange: (Float) -> Unit = { newValue ->
    val valueChange: (Float) -> Unit = { newValue ->
        hapticsViewModel?.onValueChange(newValue)
        hapticsViewModel?.addVelocityDataPoint(newValue)
        onValueChanged(newValue)
        onValueChanged(newValue)
    }
    }
    val semantics =
    val semantics =
@@ -193,23 +188,22 @@ private fun Haptics.createViewModel(
                            interactionSource,
                            interactionSource,
                            valueRange,
                            valueRange,
                            orientation,
                            orientation,
                            VolumeHapticsConfigsProvider.sliderHapticFeedbackConfig(
                            hapticConfigs.hapticFeedbackConfig,
                                valueRange,
                            hapticConfigs.sliderTrackerConfig,
                                hapticFilter,
                            ),
                            VolumeHapticsConfigsProvider.seekableSliderTrackerConfig,
                        )
                        )
                    }
                    }
                    .also { hapticsViewModel ->
                    .also { hapticsViewModel ->
                        var lastDiscreteStep by remember { mutableFloatStateOf(value) }
                        var lastValue by remember { mutableFloatStateOf(value) }
                        LaunchedEffect(value) {
                        LaunchedEffect(value) {
                            snapshotFlow { value }
                            val roundedValue =
                                .map { round(it) }
                                if (hapticConfigs.hapticFeedbackConfig.sliderStepSize != 0f) {
                                .filter { it != lastDiscreteStep }
                                    round(value)
                                .distinctUntilChanged()
                                } else {
                                .collect { discreteStep ->
                                    value
                                    lastDiscreteStep = discreteStep
                                }
                                    hapticsViewModel.onValueChange(discreteStep)
                            if (roundedValue != lastValue) {
                                lastValue = roundedValue
                                hapticsViewModel.onValueChange(roundedValue)
                            }
                            }
                        }
                        }
                    }
                    }
@@ -228,7 +222,7 @@ sealed interface Haptics {


    data class Enabled(
    data class Enabled(
        val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
        val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
        val hapticFilter: SliderHapticFeedbackFilter,
        val hapticConfigs: VolumeHapticsConfigs,
        val orientation: Orientation,
        val orientation: Orientation,
    ) : Haptics
    ) : Haptics
}
}