Loading packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt +2 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -141,11 +142,7 @@ class SeekbarHapticPluginTest : SysuiTestCase() { } private fun createPlugin() { plugin = SeekbarHapticPlugin( vibratorHelper, kosmos.fakeSystemClock, ) plugin = SeekbarHapticPlugin(vibratorHelper, kosmos.msdlPlayer, kosmos.fakeSystemClock) } companion object { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +242 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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() { Loading @@ -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 = Loading @@ -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 = Loading @@ -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 = Loading @@ -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 = Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +14 −21 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -93,7 +86,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { brightnessSliderView, mFalsingManager, uiEventLogger, SeekbarHapticPlugin(vibratorHelper, systemClock), SeekbarHapticPlugin(vibratorHelper, msdlPlayer, systemClock), activityStarter, ) mController.init() Loading packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -39,6 +40,7 @@ class SeekbarHapticPlugin @JvmOverloads constructor( vibratorHelper: VibratorHelper, msdlPlayer: MSDLPlayer, systemClock: SystemClock, sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(), private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(), Loading @@ -63,6 +65,7 @@ constructor( private val sliderHapticFeedbackProvider = SliderHapticFeedbackProvider( vibratorHelper, msdlPlayer, dragVelocityProvider, sliderHapticFeedbackConfig, systemClock, Loading packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +35 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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 Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt +2 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -141,11 +142,7 @@ class SeekbarHapticPluginTest : SysuiTestCase() { } private fun createPlugin() { plugin = SeekbarHapticPlugin( vibratorHelper, kosmos.fakeSystemClock, ) plugin = SeekbarHapticPlugin(vibratorHelper, kosmos.msdlPlayer, kosmos.fakeSystemClock) } companion object { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt +242 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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() { Loading @@ -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 = Loading @@ -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 = Loading @@ -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 = Loading @@ -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 = Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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 Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt +14 −21 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -93,7 +86,7 @@ class BrightnessSliderControllerTest : SysuiTestCase() { brightnessSliderView, mFalsingManager, uiEventLogger, SeekbarHapticPlugin(vibratorHelper, systemClock), SeekbarHapticPlugin(vibratorHelper, msdlPlayer, systemClock), activityStarter, ) mController.init() Loading
packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt +3 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -39,6 +40,7 @@ class SeekbarHapticPlugin @JvmOverloads constructor( vibratorHelper: VibratorHelper, msdlPlayer: MSDLPlayer, systemClock: SystemClock, sliderHapticFeedbackConfig: SliderHapticFeedbackConfig = SliderHapticFeedbackConfig(), private val sliderTrackerConfig: SeekableSliderTrackerConfig = SeekableSliderTrackerConfig(), Loading @@ -63,6 +65,7 @@ constructor( private val sliderHapticFeedbackProvider = SliderHapticFeedbackProvider( vibratorHelper, msdlPlayer, dragVelocityProvider, sliderHapticFeedbackConfig, systemClock, Loading
packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt +35 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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 Loading Loading @@ -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