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

Commit 03e93eb5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Adding the MSDLPlayer to platform slider haptics." into main

parents 15344024 63df053a
Loading
Loading
Loading
Loading
+2 −5
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.widget.SeekBar
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.VibratorHelper
@@ -141,11 +142,7 @@ class SeekbarHapticPluginTest : SysuiTestCase() {
        }

    private fun createPlugin() {
        plugin =
            SeekbarHapticPlugin(
                vibratorHelper,
                kosmos.fakeSystemClock,
            )
        plugin = SeekbarHapticPlugin(vibratorHelper, kosmos.msdlPlayer, kosmos.fakeSystemClock)
    }

    companion object {
+242 −0
Original line number Diff line number Diff line
@@ -16,16 +16,26 @@

package com.android.systemui.haptics.slider

import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.fakeVibratorHelper
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,6 +45,7 @@ import org.junit.runner.RunWith
class SliderHapticFeedbackProviderTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val config = SliderHapticFeedbackConfig()

@@ -44,7 +55,14 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
    private val dragTextureThresholdMillis =
        lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
    private val vibratorHelper = kosmos.fakeVibratorHelper
    private val msdlPlayer = kosmos.fakeMSDLPlayer
    private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider
    private val pipeliningAttributes =
        VibrationAttributes.Builder()
            .setUsage(VibrationAttributes.USAGE_TOUCH)
            .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
            .build()
    private lateinit var dynamicProperties: InteractionProperties.DynamicVibrationScale

    @Before
    fun setup() {
@@ -54,13 +72,20 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        sliderHapticFeedbackProvider =
            SliderHapticFeedbackProvider(
                vibratorHelper,
                msdlPlayer,
                dragVelocityProvider,
                config,
                kosmos.fakeSystemClock,
            )
        dynamicProperties =
            InteractionProperties.DynamicVibrationScale(
                sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                pipeliningAttributes,
            )
    }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_playsClick() =
        with(kosmos) {
            val vibration =
@@ -77,6 +102,18 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_playsDragThresholdLimitToken() =
        testScope.runTest {
            sliderHapticFeedbackProvider.onLowerBookend()

            assertThat(msdlPlayer.latestTokenPlayed)
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
        with(kosmos) {
            val vibration =
@@ -94,6 +131,20 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
        testScope.runTest {
            sliderHapticFeedbackProvider.onLowerBookend()
            sliderHapticFeedbackProvider.onLowerBookend()

            assertThat(msdlPlayer.latestTokenPlayed)
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_playsClick() =
        with(kosmos) {
            val vibration =
@@ -110,6 +161,18 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_playsDragThresholdLimitToken() =
        testScope.runTest {
            sliderHapticFeedbackProvider.onUpperBookend()

            assertThat(msdlPlayer.latestTokenPlayed)
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
        with(kosmos) {
            val vibration =
@@ -127,6 +190,20 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_twoTimes_playsDragThresholdLimitTokenOnlyOnce() =
        testScope.runTest {
            sliderHapticFeedbackProvider.onUpperBookend()
            sliderHapticFeedbackProvider.onUpperBookend()

            assertThat(msdlPlayer.latestTokenPlayed)
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(dynamicProperties)
            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
@@ -150,6 +227,31 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_onQuickSuccession_playsContinuousDragTokenOnce() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
            val progress = 1f
            val expectedScale =
                sliderHapticFeedbackProvider.scaleOnDragTexture(config.maxVelocityToScale, progress)
            val expectedProperties =
                InteractionProperties.DynamicVibrationScale(expectedScale, pipeliningAttributes)

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

            // WHEN two calls to play occur immediately
            sliderHapticFeedbackProvider.onProgress(progress)
            sliderHapticFeedbackProvider.onProgress(progress)

            // THEN the correct token plays once
            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
        with(kosmos) {
            // GIVEN max velocity and a slider progress at half progress
@@ -175,6 +277,41 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_beforeNextDragThreshold_playsContinousDragTokenOnce() =
        with(kosmos) {
            // GIVEN max velocity and a slider progress at half progress
            val firstProgress = 0.5f

            // 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
            fakeSystemClock.advanceTime(1000)

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

            // THEN Only the first event plays the expected token and propertiesv
            val expectedProperties =
                InteractionProperties.DynamicVibrationScale(
                    sliderHapticFeedbackProvider.scaleOnDragTexture(
                        config.maxVelocityToScale,
                        firstProgress,
                    ),
                    pipeliningAttributes,
                )
            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
            assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
            assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
        with(kosmos) {
            // GIVEN max velocity and a slider progress at half progress
@@ -200,6 +337,51 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtProgress_afterNextDragThreshold_playsContinuousDragTokenTwice() =
        with(kosmos) {
            // GIVEN max velocity and a slider progress at half progress
            val firstProgress = 0.5f

            // Given a second slider progress event beyond progress threshold
            val secondProgress = firstProgress + config.deltaProgressForDragThreshold + 0.01f

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

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

            // THEN the correct token plays twice with the correct properties
            val firstProperties =
                InteractionProperties.DynamicVibrationScale(
                    sliderHapticFeedbackProvider.scaleOnDragTexture(
                        config.maxVelocityToScale,
                        firstProgress,
                    ),
                    pipeliningAttributes,
                )
            val secondProperties =
                InteractionProperties.DynamicVibrationScale(
                    sliderHapticFeedbackProvider.scaleOnDragTexture(
                        config.maxVelocityToScale,
                        secondProgress,
                    ),
                    pipeliningAttributes,
                )

            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
            assertThat(msdlPlayer.tokensPlayed[0]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(firstProperties)
            assertThat(msdlPlayer.tokensPlayed[1]).isEqualTo(MSDLToken.DRAG_INDICATOR_CONTINUOUS)
            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(secondProperties)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
@@ -233,6 +415,36 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTokensTwice() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
            val progress = 1f
            val expectedProperties =
                InteractionProperties.DynamicVibrationScale(
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                    pipeliningAttributes,
                )

            // GIVEN a vibration at the lower bookend followed by a request to vibrate at progress
            sliderHapticFeedbackProvider.onLowerBookend()
            sliderHapticFeedbackProvider.onProgress(progress)

            // WHEN a vibration is to trigger again at the lower bookend
            sliderHapticFeedbackProvider.onLowerBookend()

            // THEN there are two bookend token vibrations
            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
            assertThat(msdlPlayer.tokensPlayed[0])
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
            assertThat(msdlPlayer.tokensPlayed[1])
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
        }

    @Test
    @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
@@ -265,6 +477,36 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
            )
        }

    @Test
    @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
    fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTokensTwice() =
        with(kosmos) {
            // GIVEN max velocity and slider progress
            val progress = 1f
            val expectedProperties =
                InteractionProperties.DynamicVibrationScale(
                    sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
                    pipeliningAttributes,
                )

            // GIVEN a vibration at the upper bookend followed by a request to vibrate at progress
            sliderHapticFeedbackProvider.onUpperBookend()
            sliderHapticFeedbackProvider.onProgress(progress)

            // WHEN a vibration is to trigger again at the upper bookend
            sliderHapticFeedbackProvider.onUpperBookend()

            // THEN there are two bookend vibrations
            assertThat(msdlPlayer.getHistory().size).isEqualTo(2)
            assertThat(msdlPlayer.tokensPlayed[0])
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.propertiesPlayed[0]).isEqualTo(expectedProperties)
            assertThat(msdlPlayer.tokensPlayed[1])
                .isEqualTo(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT)
            assertThat(msdlPlayer.propertiesPlayed[1]).isEqualTo(expectedProperties)
        }

    @Test
    fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
        with(kosmos) {
            // GIVEN max velocity and a slider progress at half progress
+14 −21
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.android.msdl.domain.MSDLPlayer
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -46,34 +47,26 @@ import org.mockito.Mockito.isNull
import org.mockito.Mockito.never
import org.mockito.Mockito.notNull
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
class BrightnessSliderControllerTest : SysuiTestCase() {

    @Mock
    private lateinit var brightnessSliderView: BrightnessSliderView
    @Mock
    private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
    @Mock
    private lateinit var mirrorController: BrightnessMirrorController
    @Mock
    private lateinit var mirror: ToggleSlider
    @Mock
    private lateinit var motionEvent: MotionEvent
    @Mock
    private lateinit var listener: ToggleSlider.Listener
    @Mock
    private lateinit var vibratorHelper: VibratorHelper
    @Mock
    private lateinit var activityStarter: ActivityStarter
    @Mock private lateinit var brightnessSliderView: BrightnessSliderView
    @Mock private lateinit var enforcedAdmin: RestrictedLockUtils.EnforcedAdmin
    @Mock private lateinit var mirrorController: BrightnessMirrorController
    @Mock private lateinit var mirror: ToggleSlider
    @Mock private lateinit var motionEvent: MotionEvent
    @Mock private lateinit var listener: ToggleSlider.Listener
    @Mock private lateinit var vibratorHelper: VibratorHelper
    @Mock private lateinit var msdlPlayer: MSDLPlayer
    @Mock private lateinit var activityStarter: ActivityStarter

    @Captor
    private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
    @Mock
    private lateinit var seekBar: SeekBar
    @Mock private lateinit var seekBar: SeekBar
    private val uiEventLogger = UiEventLoggerFake()
    private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
    private val systemClock = FakeSystemClock()
@@ -93,7 +86,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() {
                brightnessSliderView,
                mFalsingManager,
                uiEventLogger,
                SeekbarHapticPlugin(vibratorHelper, systemClock),
                SeekbarHapticPlugin(vibratorHelper, msdlPlayer, systemClock),
                activityStarter,
            )
        mController.init()
+3 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.widget.SeekBar
import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.time.SystemClock
import com.google.android.msdl.domain.MSDLPlayer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -39,6 +40,7 @@ class SeekbarHapticPlugin
@JvmOverloads
constructor(
    vibratorHelper: VibratorHelper,
    msdlPlayer: MSDLPlayer,
    systemClock: SystemClock,
    sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
    private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(),
@@ -63,6 +65,7 @@ constructor(
    private val sliderHapticFeedbackProvider =
        SliderHapticFeedbackProvider(
            vibratorHelper,
            msdlPlayer,
            dragVelocityProvider,
            sliderHapticFeedbackConfig,
            systemClock,
+35 −11
Original line number Diff line number Diff line
@@ -22,7 +22,11 @@ import android.view.VelocityTracker
import android.view.animation.AccelerateInterpolator
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import com.android.systemui.Flags
import com.android.systemui.statusbar.VibratorHelper
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.InteractionProperties
import com.google.android.msdl.domain.MSDLPlayer
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.pow
@@ -38,6 +42,7 @@ import kotlin.math.pow
 */
class SliderHapticFeedbackProvider(
    private val vibratorHelper: VibratorHelper,
    private val msdlPlayer: MSDLPlayer,
    private val velocityProvider: SliderDragVelocityProvider,
    private val config: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(),
    private val clock: com.android.systemui.util.time.SystemClock,
@@ -67,12 +72,21 @@ class SliderHapticFeedbackProvider(
     */
    private fun vibrateOnEdgeCollision(absoluteVelocity: Float) {
        val powerScale = scaleOnEdgeCollision(absoluteVelocity)
        if (Flags.msdlFeedback()) {
            val properties =
                InteractionProperties.DynamicVibrationScale(
                    powerScale,
                    VIBRATION_ATTRIBUTES_PIPELINING,
                )
            msdlPlayer.playToken(MSDLToken.DRAG_THRESHOLD_INDICATOR_LIMIT, properties)
        } else {
            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
@@ -112,14 +126,24 @@ class SliderHapticFeedbackProvider(

        val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)

        // Trigger the vibration composition
        // Deliver haptic feedback
        performContinuousSliderDragVibration(powerScale)
        dragTextureLastTime = currentTime
        dragTextureLastProgress = normalizedSliderProgress
    }

    private fun performContinuousSliderDragVibration(scale: Float) {
        if (Flags.msdlFeedback()) {
            val properties =
                InteractionProperties.DynamicVibrationScale(scale, VIBRATION_ATTRIBUTES_PIPELINING)
            msdlPlayer.playToken(MSDLToken.DRAG_INDICATOR_CONTINUOUS, properties)
        } else {
            val composition = VibrationEffect.startComposition()
            repeat(config.numberOfLowTicks) {
            composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, powerScale)
                composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale)
            }
            vibratorHelper.vibrate(composition.compose(), VIBRATION_ATTRIBUTES_PIPELINING)
        dragTextureLastTime = currentTime
        dragTextureLastProgress = normalizedSliderProgress
        }
    }

    /**
Loading