summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Nick Chameyev <nickchameyev@google.com> 2022-11-07 12:11:23 +0000
committer Nick Chameyev <nickchameyev@google.com> 2022-12-12 10:16:36 +0000
commit61e549a2322b9dde025ee92a7f1460f9da05811c (patch)
treefe5685684469f3d78a40bdff66b4b454d0d11ab4
parent67c61bf59b52fd687e46e0661fc3a13afdd6baba (diff)
[Unfold transition] Add haptics effect
Adds haptics effect when the animation is about to end. The effect is played when the animation is cancelled because of timeout or when the device is unfolded quickly. Bug: 200555479 Test: atest com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProviderTest Test: manual test Change-Id: I0d6c8098b1c86e37547793644bf450d69c166c50
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt32
-rw-r--r--packages/SystemUI/unfold/Android.bp1
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt22
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt1
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt4
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt6
9 files changed, 161 insertions, 2 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 6dc4f5c60108..68f4dbe53500 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -125,10 +125,10 @@ public interface SysUIComponent {
default void init() {
// Initialize components that have no direct tie to the dagger dependency graph,
// but are critical to this component's operation
- // TODO(b/205034537): I think this is a good idea?
getSysUIUnfoldComponent().ifPresent(c -> {
c.getUnfoldLightRevealOverlayAnimation().init();
c.getUnfoldTransitionWallpaperController().init();
+ c.getUnfoldHapticsPlayer();
});
getNaturalRotationUnfoldProgressProvider().ifPresent(o -> o.init());
// No init method needed, just needs to be gotten so that it's created.
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 13ac39c77c93..209d93f786a2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -92,5 +92,7 @@ interface SysUIUnfoldComponent {
fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController
+ fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer
+
fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
new file mode 100644
index 000000000000..7726d09cf971
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.unfold
+
+import android.os.SystemProperties
+import android.os.VibrationEffect
+import android.os.Vibrator
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import javax.inject.Inject
+
+/**
+ * Class that plays a haptics effect during unfolding a foldable device
+ */
+@SysUIUnfoldScope
+class UnfoldHapticsPlayer
+@Inject
+constructor(
+ unfoldTransitionProgressProvider: UnfoldTransitionProgressProvider,
+ private val vibrator: Vibrator?
+) : TransitionProgressListener {
+
+ init {
+ if (vibrator != null) {
+ // We don't need to remove the callback because we should listen to it
+ // the whole time when SystemUI process is alive
+ unfoldTransitionProgressProvider.addCallback(this)
+ }
+ }
+
+ private var lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+
+ override fun onTransitionStarted() {
+ lastTransitionProgress = TRANSITION_PROGRESS_CLOSED
+ }
+
+ override fun onTransitionProgress(progress: Float) {
+ lastTransitionProgress = progress
+ }
+
+ override fun onTransitionFinishing() {
+ // Run haptics only if the animation is long enough to notice
+ if (lastTransitionProgress < TRANSITION_NOTICEABLE_THRESHOLD) {
+ playHaptics()
+ }
+ }
+
+ override fun onTransitionFinished() {
+ lastTransitionProgress = TRANSITION_PROGRESS_FULL_OPEN
+ }
+
+ private fun playHaptics() {
+ vibrator?.vibrate(effect)
+ }
+
+ private val hapticsScale: Float
+ get() {
+ val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
+ return intensityString.toFloatOrNull() ?: 0.1f
+ }
+
+ private val hapticsScaleTick: Float
+ get() {
+ val intensityString =
+ SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
+ return intensityString.toFloatOrNull() ?: 0.6f
+ }
+
+ private val primitivesCount: Int
+ get() {
+ val count = SystemProperties.get("persist.unfold.primitives_count", "18")
+ return count.toIntOrNull() ?: 18
+ }
+
+ private val effect: VibrationEffect by lazy {
+ val composition =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0F, 0)
+
+ repeat(primitivesCount) {
+ composition.addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
+ hapticsScale,
+ 0
+ )
+ }
+
+ composition
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, hapticsScaleTick)
+ .compose()
+ }
+}
+
+private const val TRANSITION_PROGRESS_CLOSED = 0f
+private const val TRANSITION_PROGRESS_FULL_OPEN = 1f
+private const val TRANSITION_NOTICEABLE_THRESHOLD = 0.9f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 03fd624a8c1d..abbdab0d41f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -69,6 +69,22 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
}
@Test
+ fun testUnfold_emitsFinishingEvent() {
+ runOnMainThreadWithInterval(
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
+ { foldStateProvider.sendHingeAngleUpdate(10f) },
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE) },
+ { foldStateProvider.sendHingeAngleUpdate(90f) },
+ { foldStateProvider.sendHingeAngleUpdate(180f) },
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
+ )
+
+ with(listener.ensureTransitionFinished()) {
+ assertHasSingleFinishingEvent()
+ }
+ }
+
+ @Test
fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
runOnMainThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
@@ -157,6 +173,12 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
currentRecording!!.addProgress(progress)
}
+ override fun onTransitionFinishing() {
+ assertWithMessage("Received transition finishing event when it's not started")
+ .that(currentRecording).isNotNull()
+ currentRecording!!.onFinishing()
+ }
+
override fun onTransitionFinished() {
assertWithMessage("Received transition finish event when it's not started")
.that(currentRecording).isNotNull()
@@ -171,6 +193,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
class UnfoldTransitionRecording {
private val progressHistory: MutableList<Float> = arrayListOf()
+ private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
assertThat(progress).isAtMost(1.0f)
@@ -179,6 +202,10 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
progressHistory += progress
}
+ fun onFinishing() {
+ finishingInvocations++
+ }
+
fun assertIncreasingProgress() {
assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS)
assertThat(progressHistory).isInOrder()
@@ -206,6 +233,11 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
.isInOrder(Comparator.reverseOrder<Float>())
assertThat(progressHistory.last()).isEqualTo(0.0f)
}
+
+ fun assertHasSingleFinishingEvent() {
+ assertWithMessage("onTransitionFinishing callback should be invoked exactly " +
+ "one time").that(finishingInvocations).isEqualTo(1)
+ }
}
private companion object {
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 108295b90e58..180b611aa13b 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -33,6 +33,7 @@ android_library {
"dagger2",
"jsr330",
],
+ kotlincflags: ["-Xjvm-default=enable"],
java_version: "1.8",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index 7117aafba54a..fee485d97afa 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -34,8 +34,28 @@ interface UnfoldTransitionProgressProvider : CallbackController<TransitionProgre
fun destroy()
interface TransitionProgressListener {
+ /** Called when transition is started */
+ @JvmDefault
fun onTransitionStarted() {}
- fun onTransitionFinished() {}
+
+ /**
+ * Called whenever transition progress is updated, [progress] is a value of the animation
+ * where 0 is fully folded, 1 is fully unfolded
+ */
+ @JvmDefault
fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
+
+ /**
+ * Called when the progress provider determined that the transition is about to finish soon.
+ *
+ * For example, in [PhysicsBasedUnfoldTransitionProgressProvider] this could happen when the
+ * animation is not tied to the hinge angle anymore and it is about to run fixed animation.
+ */
+ @JvmDefault
+ fun onTransitionFinishing() {}
+
+ /** Called when transition is completely finished */
+ @JvmDefault
+ fun onTransitionFinished() {}
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 4c85b055aeae..fa59cb4d12be 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
@@ -88,6 +88,7 @@ internal class FixedTimingTransitionProgressProvider(
override fun onAnimationStart(animator: Animator) {
listeners.forEach { it.onTransitionStarted() }
+ listeners.forEach { it.onTransitionFinishing() }
}
override fun onAnimationEnd(animator: Animator) {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index b56818693124..24394ed02823 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -124,6 +124,10 @@ class PhysicsBasedUnfoldTransitionProgressProvider(
private fun cancelTransition(endValue: Float, animate: Boolean) {
if (isTransitionRunning && animate) {
+ if (endValue == 1.0f && !isAnimatedCancelRunning) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
+
isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue)
} else {
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 8491f832b740..b7bab3e5ed5a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -110,6 +110,12 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
lastTransitionProgress = progress
}
+ override fun onTransitionFinishing() {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
+ }
+
override fun onTransitionFinished() {
if (isReadyToHandleTransition) {
listeners.forEach { it.onTransitionFinished() }