diff options
| author | 2022-11-07 12:11:23 +0000 | |
|---|---|---|
| committer | 2022-12-12 10:16:36 +0000 | |
| commit | 61e549a2322b9dde025ee92a7f1460f9da05811c (patch) | |
| tree | fe5685684469f3d78a40bdff66b4b454d0d11ab4 | |
| parent | 67c61bf59b52fd687e46e0661fc3a13afdd6baba (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
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() }  |