summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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() }