summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/slider/SliderQuantization.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java3
5 files changed, 130 insertions, 3 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index 3467382df4da..75fd56658d9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -47,7 +47,7 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val config = SliderHapticFeedbackConfig()
+ private var config = SliderHapticFeedbackConfig()
private val dragVelocityProvider = SliderDragVelocityProvider { config.maxVelocityToScale }
@@ -227,6 +227,72 @@ class SliderHapticFeedbackProviderTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtProgress_forDiscreteSlider_playsTick() =
+ with(kosmos) {
+ config = SliderHapticFeedbackConfig(sliderStepSize = 0.2f)
+ sliderHapticFeedbackProvider =
+ SliderHapticFeedbackProvider(
+ vibratorHelper,
+ msdlPlayer,
+ dragVelocityProvider,
+ config,
+ kosmos.fakeSystemClock,
+ )
+
+ // GIVEN max velocity and slider progress
+ val progress = 1f
+ val expectedScale =
+ sliderHapticFeedbackProvider.scaleOnDragTexture(config.maxVelocityToScale, progress)
+ val tick =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, expectedScale)
+ .compose()
+
+ // GIVEN system running for 1s
+ fakeSystemClock.advanceTime(1000)
+
+ // WHEN called to play haptics
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // THEN the correct composition only plays once
+ assertEquals(expected = 1, vibratorHelper.timesVibratedWithEffect(tick))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ fun playHapticAtProgress_forDiscreteSlider_playsDiscreteSliderToken() =
+ with(kosmos) {
+ config = SliderHapticFeedbackConfig(sliderStepSize = 0.2f)
+ sliderHapticFeedbackProvider =
+ SliderHapticFeedbackProvider(
+ vibratorHelper,
+ msdlPlayer,
+ dragVelocityProvider,
+ config,
+ kosmos.fakeSystemClock,
+ )
+
+ // 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 called to play haptics
+ sliderHapticFeedbackProvider.onProgress(progress)
+
+ // THEN the correct token plays once
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_DISCRETE)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(expectedProperties)
+ assertThat(msdlPlayer.getHistory().size).isEqualTo(1)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun playHapticAtProgress_onQuickSuccession_playsContinuousDragTokenOnce() =
with(kosmos) {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 24dd04dbadd2..da124de358a8 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -47,4 +47,6 @@ data class SliderHapticFeedbackConfig(
@FloatRange(from = 0.0, to = 1.0) val lowerBookendScale: Float = 0.05f,
/** Exponent for power function compensation */
@FloatRange(from = 0.0, fromInclusive = false) val exponent: Float = 1f / 0.89f,
+ /** The step-size that defines the slider quantization. Zero represents a continuous slider */
+ @FloatRange(from = 0.0) val sliderStepSize: Float = 0f,
)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index bc4f531b8b81..de6697bd8fea 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -30,6 +30,7 @@ import com.google.android.msdl.domain.MSDLPlayer
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.pow
+import kotlin.math.round
/**
* Listener of slider events that triggers haptic feedback.
@@ -124,14 +125,45 @@ class SliderHapticFeedbackProvider(
val deltaProgress = abs(normalizedSliderProgress - dragTextureLastProgress)
if (deltaProgress < config.deltaProgressForDragThreshold) return
+ // Check if the progress is a discrete step so haptics can be delivered
+ if (
+ config.sliderStepSize > 0 &&
+ !normalizedSliderProgress.isDiscreteStep(config.sliderStepSize)
+ ) {
+ return
+ }
+
val powerScale = scaleOnDragTexture(absoluteVelocity, normalizedSliderProgress)
// Deliver haptic feedback
- performContinuousSliderDragVibration(powerScale)
+ when {
+ config.sliderStepSize == 0f -> performContinuousSliderDragVibration(powerScale)
+ config.sliderStepSize > 0f -> performDiscreteSliderDragVibration(powerScale)
+ }
dragTextureLastTime = currentTime
dragTextureLastProgress = normalizedSliderProgress
}
+ private fun Float.isDiscreteStep(stepSize: Float, epsilon: Float = 0.001f): Boolean {
+ if (stepSize <= 0f) return false
+ val division = this / stepSize
+ return abs(division - round(division)) < epsilon
+ }
+
+ private fun performDiscreteSliderDragVibration(scale: Float) {
+ if (Flags.msdlFeedback()) {
+ val properties =
+ InteractionProperties.DynamicVibrationScale(scale, VIBRATION_ATTRIBUTES_PIPELINING)
+ msdlPlayer.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE, properties)
+ } else {
+ val effect =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, scale)
+ .compose()
+ vibratorHelper.vibrate(effect, VIBRATION_ATTRIBUTES_PIPELINING)
+ }
+ }
+
private fun performContinuousSliderDragVibration(scale: Float) {
if (Flags.msdlFeedback()) {
val properties =
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderQuantization.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderQuantization.kt
new file mode 100644
index 000000000000..033d55cc9b61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderQuantization.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.slider
+
+interface SliderQuantization {
+ /** What is the step size between discrete steps of the slider */
+ val stepSize: Float
+
+ data class Continuous(override val stepSize: Float = Float.MIN_VALUE) : SliderQuantization
+
+ data class Discrete(override val stepSize: Float) : SliderQuantization
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 07509e6368fb..e74b0f3cc7db 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2697,7 +2697,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
/* velocityAxis= */ MotionEvent.AXIS_Y,
/* upperBookendScale= */ 1f,
/* lowerBookendScale= */ 0.05f,
- /* exponent= */ 1f / 0.89f);
+ /* exponent= */ 1f / 0.89f,
+ /* sliderStepSize = */ 0f);
private static final SeekableSliderTrackerConfig sSliderTrackerConfig =
new SeekableSliderTrackerConfig(
/* waitTimeMillis= */100,