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

Commit b079daa1 authored by Lucas Dupin's avatar Lucas Dupin
Browse files

Make power menu animaiton smoother

This animation was being mapped directly to the alpha of the dialog,
which makes it a bit stiff. I'm now using a physics based spring
animation, sharing logic with the shade.

Fixes: 149792636
Test: atest NotificationShadeDepthControllerTest
Change-Id: Iad8835b8040d1aedc13f0e323fc747e4ab4813e9
parent 8829c37c
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -1849,7 +1849,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                    .alpha(1)
                    .translationX(0)
                    .translationY(0)
                    .setDuration(300)
                    .setDuration(450)
                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                    .setUpdateListener(animation -> {
                        float animatedValue = animation.getAnimatedFraction();
@@ -1878,7 +1878,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
                    .alpha(0)
                    .translationX(mGlobalActionsLayout.getAnimationOffsetX())
                    .translationY(mGlobalActionsLayout.getAnimationOffsetY())
                    .setDuration(300)
                    .setDuration(550)
                    .withEndAction(this::completeDismiss)
                    .setInterpolator(new LogAccelerateInterpolator())
                    .setUpdateListener(animation -> {
+68 −49
Original line number Diff line number Diff line
@@ -67,32 +67,9 @@ class NotificationShadeDepthController @Inject constructor(
    private var updateScheduled: Boolean = false
    private var shadeExpansion = 0f
    @VisibleForTesting
    var shadeSpring = SpringAnimation(this, object :
            FloatPropertyCompat<NotificationShadeDepthController>("shadeBlurRadius") {
        override fun setValue(rect: NotificationShadeDepthController?, value: Float) {
            shadeBlurRadius = value.toInt()
        }

        override fun getValue(rect: NotificationShadeDepthController?): Float {
            return shadeBlurRadius.toFloat()
        }
    })
    private val zoomInterpolator = Interpolators.ACCELERATE_DECELERATE

    /**
     * Radius that we're animating to.
     */
    private var pendingShadeBlurRadius = -1

    /**
     * Shade blur radius on the current frame.
     */
    private var shadeBlurRadius = 0
        set(value) {
            if (field == value) return
            field = value
            scheduleUpdate()
        }
    var shadeSpring = DepthAnimation()
    @VisibleForTesting
    var globalActionsSpring = DepthAnimation()

    /**
     * Blur radius of the wake-up animation on this frame.
@@ -103,7 +80,6 @@ class NotificationShadeDepthController @Inject constructor(
            field = value
            scheduleUpdate()
        }
    private var globalDialogVisibility = 0f

    /**
     * Callback that updates the window blur value and is called only once per frame.
@@ -111,12 +87,9 @@ class NotificationShadeDepthController @Inject constructor(
    private val updateBlurCallback = Choreographer.FrameCallback {
        updateScheduled = false

        val blur = max(shadeBlurRadius,
                max(wakeAndUnlockBlurRadius, blurUtils.blurRadiusOfRatio(globalDialogVisibility)))
        val blur = max(max(shadeSpring.radius, wakeAndUnlockBlurRadius), globalActionsSpring.radius)
        blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
        val rawZoom = max(blurUtils.ratioOfBlurRadius(blur), globalDialogVisibility)
        wallpaperManager.setWallpaperZoomOut(root.windowToken,
                zoomInterpolator.getInterpolation(rawZoom))
        wallpaperManager.setWallpaperZoomOut(root.windowToken, blurUtils.ratioOfBlurRadius(blur))
        notificationShadeWindowController.setBackgroundBlurRadius(blur)
    }

@@ -163,8 +136,9 @@ class NotificationShadeDepthController @Inject constructor(
        }

        override fun onDozingChanged(isDozing: Boolean) {
            if (isDozing && shadeSpring.isRunning) {
                shadeSpring.skipToEnd()
            if (isDozing) {
                shadeSpring.finishIfRunning()
                globalActionsSpring.finishIfRunning()
            }
        }
    }
@@ -174,10 +148,6 @@ class NotificationShadeDepthController @Inject constructor(
        if (WAKE_UP_ANIMATION_ENABLED) {
            keyguardStateController.addCallback(keyguardStateCallback)
        }
        shadeSpring.spring = SpringForce(0.0f)
        shadeSpring.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
        shadeSpring.spring.stiffness = SpringForce.STIFFNESS_LOW
        shadeSpring.addEndListener { _, _, _, _ -> pendingShadeBlurRadius = -1 }
        statusBarStateController.addCallback(statusBarStateCallback)
    }

@@ -198,11 +168,7 @@ class NotificationShadeDepthController @Inject constructor(
            newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
        }

        if (pendingShadeBlurRadius == newBlur) {
            return
        }
        pendingShadeBlurRadius = newBlur
        shadeSpring.animateToFinalPosition(newBlur.toFloat())
        shadeSpring.animateTo(newBlur)
    }

    private fun scheduleUpdate(viewToBlur: View? = null) {
@@ -215,19 +181,72 @@ class NotificationShadeDepthController @Inject constructor(
    }

    fun updateGlobalDialogVisibility(visibility: Float, dialogView: View) {
        if (visibility == globalDialogVisibility) {
            return
        }
        globalDialogVisibility = visibility
        scheduleUpdate(dialogView)
        globalActionsSpring.animateTo(blurUtils.blurRadiusOfRatio(visibility), dialogView)
    }

    override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
        IndentingPrintWriter(pw, "  ").let {
            it.println("StatusBarWindowBlurController:")
            it.increaseIndent()
            it.println("shadeBlurRadius: $shadeBlurRadius")
            it.println("shadeRadius: ${shadeSpring.radius}")
            it.println("globalActionsRadius: ${globalActionsSpring.radius}")
            it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
        }
    }

    /**
     * Animation helper that smoothly animates the depth using a spring and deals with frame
     * invalidation.
     */
    inner class DepthAnimation() {
        /**
         * Blur radius visible on the UI, in pixels.
         */
        var radius = 0
            private set

        /**
         * Radius that we're animating to.
         */
        private var pendingRadius = -1

        /**
         * View on {@link Surface} that wants depth.
         */
        private var view: View? = null

        private var springAnimation = SpringAnimation(this, object :
            FloatPropertyCompat<DepthAnimation>("blurRadius") {
            override fun setValue(rect: DepthAnimation?, value: Float) {
                radius = value.toInt()
                scheduleUpdate(view)
            }

            override fun getValue(rect: DepthAnimation?): Float {
                return radius.toFloat()
            }
        })

        init {
            springAnimation.spring = SpringForce(0.0f)
            springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
            springAnimation.spring.stiffness = SpringForce.STIFFNESS_MEDIUM
            springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 }
        }

        fun animateTo(newRadius: Int, viewToBlur: View? = null) {
            if (pendingRadius == newRadius && view == viewToBlur) {
                return
            }
            view = viewToBlur
            pendingRadius = newRadius
            springAnimation.animateToFinalPosition(newRadius.toFloat())
        }

        fun finishIfRunning() {
            if (springAnimation.isRunning) {
                springAnimation.skipToEnd()
            }
        }
    }
}
+13 −8
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
import android.view.View
import android.view.ViewRootImpl
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -35,10 +34,14 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.*
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit

@RunWith(AndroidTestingRunner::class)
@@ -56,7 +59,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
    @Mock private lateinit var dumpManager: DumpManager
    @Mock private lateinit var root: View
    @Mock private lateinit var viewRootImpl: ViewRootImpl
    @Mock private lateinit var shadeSpring: SpringAnimation
    @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
    @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
    @JvmField @Rule val mockitoRule = MockitoJUnit.rule()

    private lateinit var statusBarStateListener: StatusBarStateController.StateListener
@@ -76,6 +80,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
                keyguardStateController, choreographer, wallpaperManager,
                notificationShadeWindowController, dumpManager)
        notificationShadeDepthController.shadeSpring = shadeSpring
        notificationShadeDepthController.globalActionsSpring = globalActionsSpring
        notificationShadeDepthController.root = root

        val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java)
@@ -92,7 +97,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
    fun onPanelExpansionChanged_apliesBlur_ifShade() {
        notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
                false /* tracking */)
        verify(shadeSpring).animateToFinalPosition(eq(maxBlur.toFloat()))
        verify(shadeSpring).animateTo(eq(maxBlur), any())
    }

    @Test
@@ -102,13 +107,13 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {

        statusBarState = StatusBarState.KEYGUARD
        statusBarStateListener.onStateChanged(statusBarState)
        verify(shadeSpring).animateToFinalPosition(eq(0f))
        verify(shadeSpring).animateTo(eq(0), any())
    }

    @Test
    fun updateGlobalDialogVisibility_schedulesUpdate() {
    fun updateGlobalDialogVisibility_appliesBlur() {
        notificationShadeDepthController.updateGlobalDialogVisibility(0.5f, root)
        verify(choreographer).postFrameCallback(any())
        verify(globalActionsSpring).animateTo(eq(maxBlur / 2), safeEq(root))
    }

    private fun <T : Any> safeEq(value: T): T {