Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 37b5e8b9 authored by Nick Chameyev's avatar Nick Chameyev Committed by Android (Google) Code Review
Browse files

Merge "[Unfold transition] Add haptics effect" into tm-qpr-dev

parents 2016b1dc 61e549a2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -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.
+2 −0
Original line number Diff line number Diff line
@@ -92,5 +92,7 @@ interface SysUIUnfoldComponent {

    fun getUnfoldTransitionWallpaperController(): UnfoldTransitionWallpaperController

    fun getUnfoldHapticsPlayer(): UnfoldHapticsPlayer

    fun getUnfoldLightRevealOverlayAnimation(): UnfoldLightRevealOverlayAnimation
}
+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
+32 −0
Original line number Diff line number Diff line
@@ -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(
@@ -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 {
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ android_library {
        "dagger2",
        "jsr330",
    ],
    kotlincflags: ["-Xjvm-default=enable"],
    java_version: "1.8",
    min_sdk_version: "current",
    plugins: ["dagger2-compiler"],
Loading