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

Commit 1b7e9c03 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez Committed by Android (Google) Code Review
Browse files

Merge "Implementing a power scale in SliderHapticFeedbackProvider." into main

parents d0830b9a 408c5b1e
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -42,4 +42,6 @@ data class SliderHapticFeedbackConfig(
    @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
    @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
    /** Vibration scale at the lower bookend of the slider */
    /** Vibration scale at the lower bookend of the slider */
    @FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
    @FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
    /** Exponent for power function compensation */
    @FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f,
)
)
+45 −15
Original line number Original line Diff line number Diff line
@@ -21,9 +21,11 @@ import android.os.VibrationEffect
import android.view.VelocityTracker
import android.view.VelocityTracker
import android.view.animation.AccelerateInterpolator
import android.view.animation.AccelerateInterpolator
import androidx.annotation.FloatRange
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.VibratorHelper
import kotlin.math.abs
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.min
import kotlin.math.pow


/**
/**
 * Listener of slider events that triggers haptic feedback.
 * Listener of slider events that triggers haptic feedback.
@@ -63,18 +65,29 @@ class SliderHapticFeedbackProvider(
     * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
     * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
     */
     */
    private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
    private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
        val powerScale = scaleOnEdgeCollision(absoluteVelocity)
        val vibration =
            VibrationEffect.startComposition()
                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, powerScale)
                .compose()
        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
    }

    /**
     * Get the velocity-based scale at the bookends
     *
     * @param[absoluteVelocity] Velocity of the handle when it reached the bookend.
     * @return The power scale for the vibration.
     */
    @VisibleForTesting
    fun scaleOnEdgeCollision(absoluteVelocity: Float): Float {
        val velocityInterpolated =
        val velocityInterpolated =
            velocityAccelerateInterpolator.getInterpolation(
            velocityAccelerateInterpolator.getInterpolation(
                min(absoluteVelocity / config.maxVelocityToScale, 1f)
                min(absoluteVelocity / config.maxVelocityToScale, 1f)
            )
            )
        val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale
        val bookendScaleRange = config.upperBookendScale - config.lowerBookendScale
        val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale
        val bookendsHitScale = bookendScaleRange * velocityInterpolated + config.lowerBookendScale

        return bookendsHitScale.pow(config.exponent)
        val vibration =
            VibrationEffect.startComposition()
                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, bookendsHitScale)
                .compose()
        vibratorHelper.vibrate(vibration, VIBRATION_ATTRIBUTES_PIPELINING)
    }
    }


    /**
    /**
@@ -96,6 +109,31 @@ class SliderHapticFeedbackProvider(
        val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
        val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
        if (deltaProgress < config.deltaProgressForDragThreshold) return
        if (deltaProgress < config.deltaProgressForDragThreshold) return


        val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)

        // Trigger the vibration composition
        val composition = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
        }
        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
        dragTextureLastTime = currentTime
        dragTextureLastProgress = normalizedSliderProgress
    }

    /**
     * Get the scale of the drag texture vibration.
     *
     * @param[absoluteVelocity] Absolute velocity of the handle.
     * @param[normalizedSliderProgress] Progress of the slider handled normalized to the range from
     *   0F to 1F (inclusive).
     *     @return the scale of the vibration.
     */
    @VisibleForTesting
    fun scaleOnDragTexture(
        absoluteVelocity: Float,
        @FloatRange(from = 0.0, to = 1.0) normalizedSliderProgress: Float
    ): Float {
        val velocityInterpolated =
        val velocityInterpolated =
            velocityAccelerateInterpolator.getInterpolation(
            velocityAccelerateInterpolator.getInterpolation(
                min(absoluteVelocity / config.maxVelocityToScale, 1f)
                min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -113,15 +151,7 @@ class SliderHapticFeedbackProvider(


        // Total scale
        // Total scale
        val scale = positionBasedScale + velocityBasedScale
        val scale = positionBasedScale + velocityBasedScale

        return scale.pow(config.exponent)
        // Trigger the vibration composition
        val composition = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
        }
        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
        dragTextureLastTime = currentTime
        dragTextureLastProgress = normalizedSliderProgress
    }
    }


    override fun onHandleAcquiredByTouch() {}
    override fun onHandleAcquiredByTouch() {}
+22 −26
Original line number Original line Diff line number Diff line
@@ -71,7 +71,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                )
                )
                .compose()
                .compose()


@@ -86,7 +86,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
                )
                )
                .compose()
                .compose()


@@ -102,7 +102,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                )
                )
                .compose()
                .compose()


@@ -117,7 +117,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                )
                )
                .compose()
                .compose()


@@ -132,7 +132,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
    fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
        // GIVEN max velocity and slider progress
        // GIVEN max velocity and slider progress
        val progress = 1f
        val progress = 1f
        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
        val expectedScale =
            sliderHapticFeedbackProvider.scaleOnDragTexture(
                config.maxVelocityToScale,
                progress,
            )
        val ticks = VibrationEffect.startComposition()
        val ticks = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
        repeat(config.numberOfLowTicks) {
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -203,7 +207,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
        // GIVEN max velocity and slider progress
        // GIVEN max velocity and slider progress
        val progress = 1f
        val progress = 1f
        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
        val expectedScale =
            sliderHapticFeedbackProvider.scaleOnDragTexture(
                config.maxVelocityToScale,
                progress,
            )
        val ticks = VibrationEffect.startComposition()
        val ticks = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
        repeat(config.numberOfLowTicks) {
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -212,7 +220,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                )
                )
                .compose()
                .compose()


@@ -232,7 +240,11 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
        // GIVEN max velocity and slider progress
        // GIVEN max velocity and slider progress
        val progress = 1f
        val progress = 1f
        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
        val expectedScale =
            sliderHapticFeedbackProvider.scaleOnDragTexture(
                config.maxVelocityToScale,
                progress,
            )
        val ticks = VibrationEffect.startComposition()
        val ticks = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
        repeat(config.numberOfLowTicks) {
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
@@ -241,7 +253,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            VibrationEffect.startComposition()
            VibrationEffect.startComposition()
                .addPrimitive(
                .addPrimitive(
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    VibrationEffect.Composition.PRIMITIVE_CLICK,
                    scaleAtBookends(config.maxVelocityToScale)
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                )
                )
                .compose()
                .compose()


@@ -289,28 +301,12 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
    }
    }


    private fun scaleAtBookends(velocity: Float): Float {
        val range = config.upperBookendScale - config.lowerBookendScale
        val interpolatedVelocity =
            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
        return interpolatedVelocity * range + config.lowerBookendScale
    }

    private fun scaleAtProgressChange(velocity: Float, progress: Float): Float {
        val range = config.progressBasedDragMaxScale - config.progressBasedDragMinScale
        val interpolatedVelocity =
            velocityInterpolator.getInterpolation(velocity / config.maxVelocityToScale)
        val interpolatedProgress = progressInterpolator.getInterpolation(progress)
        val bump = interpolatedVelocity * config.additionalVelocityMaxBump
        return interpolatedProgress * range + config.progressBasedDragMinScale + bump
    }

    private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
    private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
        val ticks = VibrationEffect.startComposition()
        val ticks = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
        repeat(config.numberOfLowTicks) {
            ticks.addPrimitive(
            ticks.addPrimitive(
                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
                VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
                scaleAtProgressChange(velocity, progress)
                sliderHapticFeedbackProvider.scaleOnDragTexture(velocity, progress),
            )
            )
        }
        }
        return ticks.compose()
        return ticks.compose()