Loading packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +2 −0 Original line number Diff line number Diff line Loading @@ -92,5 +92,7 @@ interface SysUIUnfoldComponent { fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation } packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt 0 → 100644 +93 −0 Original line number Diff line number Diff line 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 packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +32 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,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( Loading Loading @@ -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() Loading @@ -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) Loading @@ -179,6 +202,10 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { progressHistory += progress } fun onFinishing() { finishingInvocations++ } fun assertIncreasingProgress() { assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS) assertThat(progressHistory).isInOrder() Loading Loading @@ -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 { Loading packages/SystemUI/unfold/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ android_library { "dagger2", "jsr330", ], kotlincflags: ["-Xjvm-default=enable"], java_version: "1.8", min_sdk_version: "current", plugins: ["dagger2-compiler"], Loading Loading
packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading
packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt +2 −0 Original line number Diff line number Diff line Loading @@ -92,5 +92,7 @@ interface SysUIUnfoldComponent { fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation }
packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt 0 → 100644 +93 −0 Original line number Diff line number Diff line 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
packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +32 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,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( Loading Loading @@ -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() Loading @@ -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) Loading @@ -179,6 +202,10 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { progressHistory += progress } fun onFinishing() { finishingInvocations++ } fun assertIncreasingProgress() { assertThat(progressHistory.size).isGreaterThan(MIN_ANIMATION_EVENTS) assertThat(progressHistory).isInOrder() Loading Loading @@ -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 { Loading
packages/SystemUI/unfold/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ android_library { "dagger2", "jsr330", ], kotlincflags: ["-Xjvm-default=enable"], java_version: "1.8", min_sdk_version: "current", plugins: ["dagger2-compiler"], Loading