summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt449
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt40
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt78
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt21
4 files changed, 376 insertions, 212 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 66fdf538e284..933ddb5739e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -16,25 +16,22 @@
package com.android.systemui.haptics.slider
-import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.view.VelocityTracker
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
import kotlin.math.max
import kotlin.test.assertEquals
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@@ -42,10 +39,10 @@ import org.mockito.MockitoAnnotations
class SliderHapticFeedbackProviderTest : SysuiTestCase() {
@Mock private lateinit var velocityTracker: VelocityTracker
- @Mock private lateinit var vibratorHelper: VibratorHelper
+
+ private val kosmos = testKosmos()
private val config = SliderHapticFeedbackConfig()
- private val clock = FakeSystemClock()
private val lowTickDuration = 12 // Mocked duration of a low tick
private val dragTextureThresholdMillis =
@@ -55,250 +52,278 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- whenever(vibratorHelper.getPrimitiveDurations(any()))
- .thenReturn(intArrayOf(lowTickDuration))
whenever(velocityTracker.isAxisSupported(config.velocityAxis)).thenReturn(true)
whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
.thenReturn(config.maxVelocityToScale)
+
+ kosmos.vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_LOW_TICK] =
+ lowTickDuration
sliderHapticFeedbackProvider =
- SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, config, clock)
+ SliderHapticFeedbackProvider(
+ kosmos.vibratorHelper,
+ velocityTracker,
+ config,
+ kosmos.fakeSystemClock,
+ )
}
@Test
- fun playHapticAtLowerBookend_playsClick() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- sliderHapticFeedbackProvider.onLowerBookend()
-
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtLowerBookend_playsClick() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
@Test
- fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
- )
- .compose()
-
- sliderHapticFeedbackProvider.onLowerBookend()
- sliderHapticFeedbackProvider.onLowerBookend()
-
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ fun playHapticAtLowerBookend_twoTimes_playsClickOnlyOnce() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale)
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onLowerBookend()
+ sliderHapticFeedbackProvider.onLowerBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
@Test
- fun playHapticAtUpperBookend_playsClick() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- sliderHapticFeedbackProvider.onUpperBookend()
+ fun playHapticAtUpperBookend_playsClick() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertTrue(vibratorHelper.hasVibratedWithEffects(vibration))
+ }
- verify(vibratorHelper).vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ @Test
+ fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() =
+ with(kosmos) {
+ val vibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ sliderHapticFeedbackProvider.onUpperBookend()
+ sliderHapticFeedbackProvider.onUpperBookend()
+
+ assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(vibration))
+ }
@Test
- fun playHapticAtUpperBookend_twoTimes_playsClickOnlyOnce() {
- val vibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
+ fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
)
- .compose()
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
- sliderHapticFeedbackProvider.onUpperBookend()
- sliderHapticFeedbackProvider.onUpperBookend()
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- verify(vibratorHelper, times(1))
- .vibrate(eq(vibration), any(VibrationAttributes::class.java))
- }
+ // WHEN two calls to play occur immediately
+ sliderHapticFeedbackProvider.onProgress(progress)
+ sliderHapticFeedbackProvider.onProgress(progress)
- @Test
- fun playHapticAtProgress_onQuickSuccession_playsLowTicksOnce() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
- )
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ // THEN the correct composition only plays once
+ assertEquals(/* expected=*/ 1, vibratorHelper.timesVibratedWithEffect(ticks.compose()))
}
- // GIVEN system running for 1s
- clock.advanceTime(1000)
-
- // WHEN two calls to play occur immediately
- sliderHapticFeedbackProvider.onProgress(progress)
- sliderHapticFeedbackProvider.onProgress(progress)
-
- // THEN the correct composition only plays once
- verify(vibratorHelper, times(1))
- .vibrate(eq(ticks.compose()), any(VibrationAttributes::class.java))
- }
-
@Test
- 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))
- }
+ fun playHapticAtProgress_beforeNextDragThreshold_playsLowTicksOnce() =
+ with(kosmos) {
+ // 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
+ 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 compositions plays
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+ assertEquals(/* expected= */ 1, vibratorHelper.totalVibrations)
+ }
@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 (time and progress)
- sliderHapticFeedbackProvider.onProgress(firstProgress)
- clock.advanceTime(dragTextureThresholdMillis.toLong())
- sliderHapticFeedbackProvider.onProgress(secondProgress)
-
- // 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))
- }
+ fun playHapticAtProgress_afterNextDragThreshold_playsLowTicksTwice() =
+ with(kosmos) {
+ // 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
+ 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 compositions play
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(firstTicks))
+ assertEquals(/* expected= */ 1, vibratorHelper.timesVibratedWithEffect(secondTicks))
+ }
@Test
- fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
+ fun playHapticAtLowerBookend_afterPlayingAtProgress_playsTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
+ val bookendVibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ // 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 vibrations
+ assertEquals(
+ /* expected= */ 2,
+ vibratorHelper.timesVibratedWithEffect(bookendVibration)
)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
}
- val bookendVibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- // 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 vibrations
- verify(vibratorHelper, times(2))
- .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
- }
@Test
- fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() {
- // GIVEN max velocity and slider progress
- val progress = 1f
- val expectedScale =
- sliderHapticFeedbackProvider.scaleOnDragTexture(
- config.maxVelocityToScale,
- progress,
+ fun playHapticAtUpperBookend_afterPlayingAtProgress_playsTwice() =
+ with(kosmos) {
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(
+ config.maxVelocityToScale,
+ progress,
+ )
+ val ticks = VibrationEffect.startComposition()
+ repeat(config.numberOfLowTicks) {
+ ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
+ }
+ val bookendVibration =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_CLICK,
+ sliderHapticFeedbackProvider.scaleOnEdgeCollision(
+ config.maxVelocityToScale
+ ),
+ )
+ .compose()
+
+ // 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
+ assertEquals(
+ /* expected= */ 2,
+ vibratorHelper.timesVibratedWithEffect(bookendVibration)
)
- val ticks = VibrationEffect.startComposition()
- repeat(config.numberOfLowTicks) {
- ticks.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, expectedScale)
}
- val bookendVibration =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_CLICK,
- sliderHapticFeedbackProvider.scaleOnEdgeCollision(config.maxVelocityToScale),
- )
- .compose()
-
- // 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()
+ fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
- // THEN there are two bookend vibrations
- verify(vibratorHelper, times(2))
- .vibrate(eq(bookendVibration), any(VibrationAttributes::class.java))
- }
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- fun dragTextureLastProgress_afterDragTextureHaptics_keepsLastDragTextureProgress() {
- // GIVEN max velocity and a slider progress at half progress
- val progress = 0.5f
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
- // 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)
- }
+ // 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
+ fun dragTextureLastProgress_afterDragTextureHaptics_resetsOnHandleReleased() =
+ with(kosmos) {
+ // GIVEN max velocity and a slider progress at half progress
+ val progress = 0.5f
- // GIVEN system running for 1s
- clock.advanceTime(1000)
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
- // WHEN a drag texture plays
- sliderHapticFeedbackProvider.onProgress(progress)
+ // WHEN a drag texture plays
+ sliderHapticFeedbackProvider.onProgress(progress)
- // WHEN the handle is released
- sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
+ // WHEN the handle is released
+ sliderHapticFeedbackProvider.onHandleReleasedFromTouch()
- // THEN the dragTextureLastProgress tracker is reset
- assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
- }
+ // THEN the dragTextureLastProgress tracker is reset
+ assertEquals(-1f, sliderHapticFeedbackProvider.dragTextureLastProgress)
+ }
private fun generateTicksComposition(velocity: Float, progress: Float): VibrationEffect {
val ticks = VibrationEffect.startComposition()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
new file mode 100644
index 000000000000..875f6ed8d4a8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/EmptyVibrator.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import android.os.Vibrator
+
+/** A simple empty vibrator required for the [FakeVibratorHelper] */
+class EmptyVibrator : Vibrator() {
+ override fun cancel() {}
+
+ override fun cancel(usageFilter: Int) {}
+
+ override fun hasAmplitudeControl(): Boolean = true
+
+ override fun hasVibrator(): Boolean = true
+
+ override fun vibrate(
+ uid: Int,
+ opPkg: String,
+ vibe: VibrationEffect,
+ reason: String,
+ attributes: VibrationAttributes,
+ ) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
new file mode 100644
index 000000000000..4c0b132210f1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/FakeVibratorHelper.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import android.annotation.SuppressLint
+import android.media.AudioAttributes
+import android.os.VibrationAttributes
+import android.os.VibrationEffect
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+
+/** A fake [VibratorHelper] that only keeps track of the latest vibration effects delivered */
+@SuppressLint("VisibleForTests")
+class FakeVibratorHelper : VibratorHelper(EmptyVibrator(), FakeExecutor(FakeSystemClock())) {
+
+ /** A customizable map of primitive ids and their durations in ms */
+ val primitiveDurations: HashMap<Int, Int> = ALL_PRIMITIVE_DURATIONS
+
+ private val vibrationEffectHistory = ArrayList<VibrationEffect>()
+
+ val totalVibrations: Int
+ get() = vibrationEffectHistory.size
+
+ override fun vibrate(effect: VibrationEffect) {
+ vibrationEffectHistory.add(effect)
+ }
+
+ override fun vibrate(effect: VibrationEffect, attributes: VibrationAttributes) = vibrate(effect)
+
+ override fun vibrate(effect: VibrationEffect, attributes: AudioAttributes) = vibrate(effect)
+
+ override fun vibrate(
+ uid: Int,
+ opPkg: String?,
+ vibe: VibrationEffect,
+ reason: String?,
+ attributes: VibrationAttributes,
+ ) = vibrate(vibe)
+
+ override fun getPrimitiveDurations(vararg primitiveIds: Int): IntArray =
+ primitiveIds.map { primitiveDurations[it] ?: 0 }.toIntArray()
+
+ fun hasVibratedWithEffects(vararg effects: VibrationEffect): Boolean =
+ vibrationEffectHistory.containsAll(effects.toList())
+
+ fun timesVibratedWithEffect(effect: VibrationEffect): Int =
+ vibrationEffectHistory.count { it == effect }
+
+ companion object {
+ val ALL_PRIMITIVE_DURATIONS =
+ hashMapOf(
+ VibrationEffect.Composition.PRIMITIVE_NOOP to 0,
+ VibrationEffect.Composition.PRIMITIVE_CLICK to 12,
+ VibrationEffect.Composition.PRIMITIVE_THUD to 300,
+ VibrationEffect.Composition.PRIMITIVE_SPIN to 133,
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE to 150,
+ VibrationEffect.Composition.PRIMITIVE_SLOW_RISE to 500,
+ VibrationEffect.Composition.PRIMITIVE_QUICK_FALL to 100,
+ VibrationEffect.Composition.PRIMITIVE_TICK to 5,
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK to 12,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
new file mode 100644
index 000000000000..434953fb2f43
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/VibratorHelperKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.vibratorHelper by Kosmos.Fixture { FakeVibratorHelper() }