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

Commit ea36ab65 authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Adds the continuous unlock animation!

This adds the new unlock animation, which is a subtle scale and translate from the bottom part of the screen. More importantly, it connects the surface behind the keyguard to your finger when swiping, so that it's one continuous motion between the lock screen and the app/launcher.

Issei's RemoteAnimation-enabling CL was rolled back for a test breakage, so you'll need to enable it to see this:
adb shell setprop persist.wm.enable_remote_keyguard_animation 1

A few P2 issues to be fixed in follow-up CLs since this one is getting large:
- Status bar icons need to animate mid-swipe so they don't jump cut post-unlock (b/183063033)
- Launcher should use a custom animation rather than surface-based animation (b/183063432)
- The scrim should probably animate out once the surface appears rather than at the end (b/183062235)
- Handle RemoteAnimation timeout, either by restarting it, or cancelling the gesture (b/183066204)

Test: change security to swipe, unlock via a slow swipe and a fast swipe and everything in between (also, release swipe halfway)
Test: PIN/password security
Test: double tap to open a notification
Test: fingerprint bypass
Test: fingerprint from lockscreen
Bug: 169692441
Change-Id: Ic78c603b2375d36cf2170b81cca7cddbf334408b
parent 3d7ff0a8
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -145,6 +145,13 @@ public interface KeyguardViewController {
     */
    void startPreHideAnimation(Runnable finishRunnable);

    /**
     * Blocks the current touch gesture from affecting the expansion amount of the notification
     * panel. This is used after a completed unlock gesture to ignore further dragging before an
     * ACTION_UP.
     */
    void blockPanelExpansionFromCurrentTouch();

    /**
     * @return the ViewRootImpl of the View where the Keyguard is mounted.
     */
+2 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ public class KeyguardService extends Service {
    /**
     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
     */
    private static boolean sEnableRemoteKeyguardAnimation =
    static boolean sEnableRemoteKeyguardAnimation =
            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);

    private final KeyguardViewMediator mKeyguardViewMediator;
@@ -138,6 +138,7 @@ public class KeyguardService extends Service {

        @Override // Binder interface
        public void onAnimationCancelled() {
            mKeyguardViewMediator.cancelKeyguardExitAnimation();
        }
    };

+329 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.keyguard

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Matrix
import android.view.RemoteAnimationTarget
import android.view.SyncRtSurfaceTransactionApplier
import androidx.core.math.MathUtils
import com.android.internal.R
import com.android.keyguard.KeyguardViewController
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject

/**
 * Starting scale factor for the app/launcher surface behind the keyguard, when it's animating
 * in during keyguard exit.
 */
const val SURFACE_BEHIND_START_SCALE_FACTOR = 0.95f

/**
 * How much to translate the surface behind the keyguard at the beginning of the exit animation,
 * in terms of percentage of the surface's height.
 */
const val SURFACE_BEHIND_START_TRANSLATION_Y = 0.05f

/**
 * Y coordinate of the pivot point for the scale effect on the surface behind the keyguard. This
 * is expressed as percentage of the surface's height, so 0.66f means the surface will scale up
 * from the point at (width / 2, height * 0.66).
 */
const val SURFACE_BEHIND_SCALE_PIVOT_Y = 0.66f

/**
 * Dismiss amount at which to fade in the surface behind the keyguard. The surface will then animate
 * along with the dismiss amount until [DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD] is reached.
 *
 * The dismiss amount is the inverse of the notification panel expansion, which decreases as the
 * lock screen is swiped away.
 */
const val DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD = 0.1f

/**
 * Dismiss amount at which to complete the keyguard exit animation and hide the keyguard.
 *
 * The dismiss amount is the inverse of the notification panel expansion, which decreases as the
 * lock screen is swiped away.
 */
const val DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD = 0.3f

/**
 * Initiates, controls, and ends the keyguard unlock animation.
 *
 * The unlock animation transitions between the keyguard (lock screen) and the app/launcher surface
 * behind the keyguard. If the user is swiping away the keyguard, this controller will decide when
 * to animate in the surface, and synchronize its appearance with the swipe gesture. If the keyguard
 * is animating away via a canned animation (due to biometric unlock, tapping a notification, etc.)
 * this controller will play a canned animation on the surface as well.
 *
 * The surface behind the keyguard is manipulated via a RemoteAnimation passed to
 * [notifyStartKeyguardExitAnimation] by [KeyguardViewMediator].
 */
@SysUISingleton
class KeyguardUnlockAnimationController @Inject constructor(
    context: Context,
    private val keyguardStateController: KeyguardStateController,
    private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
    private val keyguardViewController: KeyguardViewController
) : KeyguardStateController.Callback {

    /**
     * Information used to start, run, and finish a RemoteAnimation on the app or launcher surface
     * behind the keyguard.
     *
     * If we're swiping to unlock, the "animation" is controlled via the gesture, tied to the
     * dismiss amounts received in [onKeyguardDismissAmountChanged]. It does not have a fixed
     * duration, and it ends when the gesture reaches a certain threshold or is cancelled.
     *
     * If we're unlocking via biometrics, PIN entry, or from clicking a notification, a canned
     * animation is started in [notifyStartKeyguardExitAnimation].
     */
    private var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
    private var surfaceBehindRemoteAnimationTarget: RemoteAnimationTarget? = null
    private var surfaceBehindRemoteAnimationStartTime: Long = 0

    /**
     * Alpha value applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
     * app/launcher behind the keyguard.
     *
     * If we're doing a swipe gesture, we fade in the surface when the swipe passes a certain
     * threshold. If we're doing a canned animation, it'll be faded in while a translate/scale
     * animation plays.
     */
    private var surfaceBehindAlpha = 1f
    private var surfaceBehindAlphaAnimator = ValueAnimator.ofFloat(0f, 1f)

    /**
     * Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
     * app/launcher behind the keyguard.
     *
     * This is used during the unlock animation/swipe gesture to scale and translate the surface.
     */
    private val surfaceBehindMatrix = Matrix()

    /**
     * Animator that animates in the surface behind the keyguard. This is used to play a canned
     * animation on the surface, if we're not doing a swipe gesture.
     */
    private val surfaceBehindEntryAnimator = ValueAnimator.ofFloat(0f, 1f)

    /** Rounded corner radius to apply to the surface behind the keyguard. */
    private var roundedCornerRadius = 0f

    init {
        surfaceBehindAlphaAnimator.duration = 150
        surfaceBehindAlphaAnimator.interpolator = Interpolators.ALPHA_IN
        surfaceBehindAlphaAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
            surfaceBehindAlpha = valueAnimator.animatedValue as Float
            updateSurfaceBehindAppearAmount()
        }
        surfaceBehindAlphaAnimator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                // If the surface alpha is 0f, it's no longer visible so we can safely be done with
                // the animation.
                if (surfaceBehindAlpha == 0f) {
                    keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation()
                }
            }
        })

        surfaceBehindEntryAnimator.duration = 450
        surfaceBehindEntryAnimator.interpolator = Interpolators.DECELERATE_QUINT
        surfaceBehindEntryAnimator.addUpdateListener { valueAnimator: ValueAnimator ->
            surfaceBehindAlpha = valueAnimator.animatedValue as Float
            setSurfaceBehindAppearAmount(valueAnimator.animatedValue as Float)
        }
        surfaceBehindEntryAnimator.addListener(object : AnimatorListenerAdapter() {
            override fun onAnimationEnd(animation: Animator) {
                keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished()
            }
        })

        // Listen for changes in the dismiss amount.
        keyguardStateController.addCallback(this)

        roundedCornerRadius =
                context.resources.getDimensionPixelSize(R.dimen.rounded_corner_radius).toFloat()
    }

    /**
     * Called from [KeyguardViewMediator] to tell us that the RemoteAnimation on the surface behind
     * the keyguard has started successfully. We can use these parameters to directly manipulate the
     * surface for the unlock gesture/animation.
     *
     * When we're done with it, we'll call [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation]
     * to end the RemoteAnimation.
     *
     * [requestedShowSurfaceBehindKeyguard] denotes whether the exit animation started because of a
     * call to [KeyguardViewMediator.showSurfaceBehindKeyguard], as happens during a swipe gesture,
     * as opposed to the keyguard hiding.
     */
    fun notifyStartKeyguardExitAnimation(
        target: RemoteAnimationTarget,
        startTime: Long,
        requestedShowSurfaceBehindKeyguard: Boolean
    ) {

        if (surfaceTransactionApplier == null) {
            surfaceTransactionApplier = SyncRtSurfaceTransactionApplier(
                    keyguardViewController.viewRootImpl.view)
        }

        surfaceBehindRemoteAnimationTarget = target
        surfaceBehindRemoteAnimationStartTime = startTime

        // If the surface behind wasn't made visible during a swipe, we'll do a canned animation
        // to animate it in. Otherwise, the swipe touch events will continue animating it.
        if (!requestedShowSurfaceBehindKeyguard) {
            keyguardViewController.hide(startTime, 350)
            surfaceBehindEntryAnimator.start()
        }
    }

    fun notifyCancelKeyguardExitAnimation() {
        surfaceBehindRemoteAnimationTarget = null
    }

    fun notifyFinishedKeyguardExitAnimation() {
        surfaceBehindRemoteAnimationTarget = null
    }

    fun hideKeyguardViewAfterRemoteAnimation() {
        keyguardViewController.hide(surfaceBehindRemoteAnimationStartTime, 350)
    }

    /**
     * Scales in and translates up the surface behind the keyguard. This is used during unlock
     * animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
     * cancelled).
     */
    private fun setSurfaceBehindAppearAmount(amount: Float) {
        if (surfaceBehindRemoteAnimationTarget == null) {
            return
        }

        val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.height()
        val scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
                (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
                MathUtils.clamp(amount, 0f, 1f))

        // Scale up from a point at the center-bottom of the surface.
        surfaceBehindMatrix.setScale(
                scaleFactor,
                scaleFactor,
                surfaceBehindRemoteAnimationTarget!!.screenSpaceBounds.width() / 2f,
                surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y)

        // Translate up from the bottom.
        surfaceBehindMatrix.postTranslate(0f,
                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount))

        // If we're snapping the keyguard back, immediately begin fading it out.
        val animationAlpha =
                if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
                else surfaceBehindAlpha

        val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
                surfaceBehindRemoteAnimationTarget!!.leash)
                .withMatrix(surfaceBehindMatrix)
                .withCornerRadius(roundedCornerRadius)
                .withAlpha(animationAlpha)
                .build()
        surfaceTransactionApplier!!.scheduleApply(params)
    }

    /**
     * Sets the appearance amount of the surface behind the keyguard, according to the current
     * keyguard dismiss amount and the method of dismissal.
     */
    private fun updateSurfaceBehindAppearAmount() {
        if (surfaceBehindRemoteAnimationTarget == null) {
            return
        }

        // For fling animations, we want to animate the surface in over the full distance. If we're
        // dismissing the keyguard via a swipe gesture (or cancelling the swipe gesture), we want to
        // bring in the surface behind over a relatively short swipe distance (~15%), to keep the
        // interaction tight.
        if (keyguardStateController.isFlingingToDismissKeyguard) {
            setSurfaceBehindAppearAmount(keyguardStateController.dismissAmount)
        } else if (keyguardStateController.isDismissingFromSwipe ||
                keyguardStateController.isSnappingKeyguardBackAfterSwipe) {
            val totalSwipeDistanceToDismiss =
                    (DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD)
            val swipedDistanceSoFar: Float =
                    keyguardStateController.dismissAmount - DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD
            val progress = swipedDistanceSoFar / totalSwipeDistanceToDismiss
            setSurfaceBehindAppearAmount(progress)
        }
    }

    override fun onKeyguardDismissAmountChanged() {
        if (!KeyguardService.sEnableRemoteKeyguardAnimation) {
            return
        }

        val dismissAmount = keyguardStateController.dismissAmount

        // Hide the keyguard if we're fully dismissed, or if we're swiping to dismiss and have
        // crossed the threshold to finish the dismissal.
        val reachedHideKeyguardThreshold = (dismissAmount >= 1f ||
                (keyguardStateController.isDismissingFromSwipe &&
                // Don't hide if we're flinging during a swipe, since we need to finish
                // animating it out. This will be called again after the fling ends.
                !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
                dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD))

        if (dismissAmount >= DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                !keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
            // We passed the threshold, and we're not yet showing the surface behind the keyguard.
            // Animate it in.
            keyguardViewMediator.get().showSurfaceBehindKeyguard()
            fadeInSurfaceBehind()
        } else if (dismissAmount < DISMISS_AMOUNT_SHOW_SURFACE_THRESHOLD &&
                keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard()) {
            // We're no longer past the threshold but we are showing the surface. Animate it out.
            keyguardViewMediator.get().hideSurfaceBehindKeyguard()
            fadeOutSurfaceBehind()
        } else if (keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe &&
                reachedHideKeyguardThreshold) {
            keyguardViewMediator.get().onKeyguardExitRemoteAnimationFinished()
        }

        if (keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
                keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
            updateSurfaceBehindAppearAmount()
        }
    }

    private fun fadeInSurfaceBehind() {
        surfaceBehindAlphaAnimator.cancel()
        surfaceBehindAlphaAnimator.start()
    }

    private fun fadeOutSurfaceBehind() {
        surfaceBehindAlphaAnimator.cancel()
        surfaceBehindAlphaAnimator.reverse()
    }
}
 No newline at end of file
+231 −51

File changed.

Preview size limit exceeded, changes collapsed.

+8 −2
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.sensors.AsyncSensorManager;
import com.android.systemui.util.settings.GlobalSettings;
@@ -92,7 +94,9 @@ public class KeyguardModule {
            NavigationModeController navigationModeController,
            KeyguardDisplayManager keyguardDisplayManager,
            DozeParameters dozeParameters,
            StatusBarStateController statusBarStateController) {
            StatusBarStateController statusBarStateController,
            KeyguardStateController keyguardStateController,
            Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController) {
        return new KeyguardViewMediator(
                context,
                falsingCollector,
@@ -109,7 +113,9 @@ public class KeyguardModule {
                navigationModeController,
                keyguardDisplayManager,
                dozeParameters,
                statusBarStateController
                statusBarStateController,
                keyguardStateController,
                keyguardUnlockAnimationController
        );
    }

Loading