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

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

Merge "Adding progress threshold for drag textures in the haptic slider" into main

parents 2e4029e4 f1deac40
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ data class SliderHapticFeedbackConfig(
    @FloatRange(from = 0.0, to = 1.0) val additionalVelocityMaxBump: Float = 0.15f,
    /** Additional time delta to wait between drag texture vibrations */
    @FloatRange(from = 0.0) val deltaMillisForDragInterval: Float = 0f,
    /** Progress threshold beyond which a new drag texture is delivered */
    @FloatRange(from = 0.0, to = 1.0) val deltaProgressForDragThreshold: Float = 0.015f,
    /** Number of low ticks in a drag texture composition. This is not expected to change */
    val numberOfLowTicks: Int = 5,
    /** Maximum velocity allowed for vibration scaling. This is not expected to change. */
+9 −1
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ class SliderHapticFeedbackProvider(
    private val positionAccelerateInterpolator =
        AccelerateInterpolator(config.progressInterpolatorFactor)
    private var dragTextureLastTime = clock.elapsedRealtime()
    var dragTextureLastProgress = -1f
        private set
    private val lowTickDurationMs =
        vibratorHelper.getPrimitiveDurations(VibrationEffect.Composition.PRIMITIVE_LOW_TICK)[0]
    private var hasVibratedAtLowerBookend = false
@@ -91,6 +93,9 @@ class SliderHapticFeedbackProvider(
        val elapsedSinceLastDrag = currentTime - dragTextureLastTime
        if (elapsedSinceLastDrag < thresholdUntilNextDragCallMillis) return

        val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
        if (deltaProgress < config.deltaProgressForDragThreshold) return

        val velocityInterpolated =
            velocityAccelerateInterpolator.getInterpolation(
                min(absoluteVelocity / config.maxVelocityToScale, 1f)
@@ -116,11 +121,14 @@ class SliderHapticFeedbackProvider(
        }
        vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
        dragTextureLastTime = currentTime
        dragTextureLastProgress = normalizedSliderProgress
    }

    override fun onHandleAcquiredByTouch() {}

    override fun onHandleReleasedFromTouch() {}
    override fun onHandleReleasedFromTouch() {
        dragTextureLastProgress = -1f
    }

    override fun onLowerBookend() {
        if (!hasVibratedAtLowerBookend) {
+84 −13
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import kotlin.math.max
import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -149,26 +151,52 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    }

    @Test
    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
        // GIVEN max velocity and slider progress
        val progress = 1f
        val expectedScale = scaleAtProgressChange(config.maxVelocityToScale.toFloat(), progress)
        val ticks = VibrationEffect.startComposition()
        repeat(config.numberOfLowTicks) {
            ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() {
        // GIVEN max velocity and a slider progress at half progress
        val firstProgress = 0.5f
        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)

        // Given a second slider progress event smaller than the progress threshold
        val secondProgress = firstProgress + max(0f, config.deltaProgressForDragThreshold - 0.01f)

        // GIVEN system running for 1s
        clock.advanceTime(1000)

        // WHEN two calls to play occur with the required threshold separation (time and progress)
        sliderHapticFeedbackProvider.onProgress(firstProgress)
        clock.advanceTime(dragTextureThresholdMillis.toLong())
        sliderHapticFeedbackProvider.onProgress(secondProgress)

        // THEN Only the first compositions plays
        verify(vibratorHelper, times(1))
            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
        verify(vibratorHelper, times(1))
            .vibrate(any(VibrationEffect::class.java), any(VibrationAttributes::class.java))
    }

    @Test
    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() {
        // GIVEN max velocity and a slider progress at half progress
        val firstProgress = 0.5f
        val firstTicks = generateTicksComposition(config.maxVelocityToScale, firstProgress)

        // Given a second slider progress event beyond progress threshold
        val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f
        val secondTicks = generateTicksComposition(config.maxVelocityToScale, secondProgress)

        // GIVEN system running for 1s
        clock.advanceTime(1000)

        // WHEN two calls to play occur with the required threshold separation
        sliderHapticFeedbackProvider.onProgress(progress)
        // WHEN two calls to play occur with the required threshold separation (time and progress)
        sliderHapticFeedbackProvider.onProgress(firstProgress)
        clock.advanceTime(dragTextureThresholdMillis.toLong())
        sliderHapticFeedbackProvider.onProgress(progress)
        sliderHapticFeedbackProvider.onProgress(secondProgress)

        // THEN the correct composition plays two times
        verify(vibratorHelper, times(2))
            .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
        // THEN the correct compositions play
        verify(vibratorHelper, times(1))
            .vibrate(eq(firstTicks), any(VibrationAttributes::class.java))
        verify(vibratorHelper, times(1))
            .vibrate(eq(secondTicks), any(VibrationAttributes::class.java))
    }

    @Test
@@ -229,6 +257,38 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
    }

    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
        // GIVEN max velocity and a slider progress at half progress
        val progress = 0.5f

        // GIVEN system running for 1s
        clock.advanceTime(1000)

        // WHEN a drag texture plays
        sliderHapticFeedbackProvider.onProgress(progress)

        // THEN the dragTextureLastProgress remembers the latest progress
        assertEquals(progress, sliderHapticFeedbackProvider.dragTextureLastProgress)
    }

    @Test
    fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() {
        // GIVEN max velocity and a slider progress at half progress
        val progress = 0.5f

        // GIVEN system running for 1s
        clock.advanceTime(1000)

        // WHEN a drag texture plays
        sliderHapticFeedbackProvider.onProgress(progress)

        // WHEN the handle is released
        sliderHapticFeedbackProvider.onHandleReleasedFromTouch()

        // THEN the dragTextureLastProgress tracker is reset
        assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
    }

    private fun scaleAtBookends(velocity: Float): Float {
        val range = config.upperBookendScale - config.lowerBookendScale
        val interpolatedVelocity =
@@ -244,4 +304,15 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        val bump = interpolatedVelocity * config.additionalVelocityMaxBump
        return interpolatedProgress * range + config.progressBasedDragMinScale + bump
    }

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