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

Commit f90ca12d authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Animate app launches from lockscreen

This CL animates the app launches (from notification, media, etc) on the
lockscreen when the lockscreen can be dismissed (i.e. when there is no
security or when SmartLock/FaceAuth is enabled).

Note that in order to try this CL, you need to enable remote keyguard
animations:

$ adb shell setprop persist.wm.enable_remote_keyguard_animation 1
$ adb reboot

See b/184726377#comment20 for before/after videos.

Bug: b/184726377
Test: Click notification/media/QS from lock screen.
Change-Id: I27e616c1549676f6d1aca53b71efc18db8eec77a
parent 56fb52b0
Loading
Loading
Loading
Loading
+59 −29
Original line number Diff line number Diff line
@@ -32,7 +32,10 @@ import kotlin.math.roundToInt
 * A class that allows activities to be started in a seamless way from a view that is transforming
 * nicely into the starting window.
 */
class ActivityLaunchAnimator(context: Context) {
class ActivityLaunchAnimator(
    private val keyguardHandler: KeyguardHandler,
    context: Context
) {
    private val TAG = this::class.java.simpleName

    companion object {
@@ -104,15 +107,22 @@ class ActivityLaunchAnimator(context: Context) {

        Log.d(TAG, "Starting intent with a launch animation")
        val runner = Runner(controller)
        val animationAdapter = RemoteAnimationAdapter(
        val isOnKeyguard = keyguardHandler.isOnKeyguard()

        // Pass the RemoteAnimationAdapter to the intent starter only if we are not on the keyguard.
        val animationAdapter = if (!isOnKeyguard) {
            RemoteAnimationAdapter(
                    runner,
                    ANIMATION_DURATION,
                    ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
            )
        } else {
            null
        }

        // Register the remote animation for the given package to also animate trampoline
        // activity launches.
        if (packageName != null) {
        if (packageName != null && animationAdapter != null) {
            try {
                ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
                    packageName, animationAdapter)
@@ -122,20 +132,30 @@ class ActivityLaunchAnimator(context: Context) {
        }

        val launchResult = intentStarter(animationAdapter)
        val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
            launchResult == ActivityManager.START_SUCCESS

        Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate")
        // Only animate if the app is not already on top and will be opened, unless we are on the
        // keyguard.
        val willAnimate =
                launchResult == ActivityManager.START_TASK_TO_FRONT ||
                        launchResult == ActivityManager.START_SUCCESS ||
                        (launchResult == ActivityManager.START_DELIVERED_TO_TOP && isOnKeyguard)

        Log.d(TAG, "launchResult=$launchResult willAnimate=$willAnimate isOnKeyguard=$isOnKeyguard")
        controller.callOnIntentStartedOnMainThread(willAnimate)

        // If we expect an animation, post a timeout to cancel it in case the remote animation is
        // never started.
        if (willAnimate) {
            runner.postTimeout()

            // Hide the keyguard using the launch animation instead of the default unlock animation.
            if (isOnKeyguard) {
                keyguardHandler.hideKeyguardWithAnimation(runner)
            }
        }
    }

    internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
    private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            this.launchContainer.context.mainExecutor.execute {
                this.onIntentStarted(willAnimate)
@@ -179,6 +199,14 @@ class ActivityLaunchAnimator(context: Context) {
        fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
    }

    interface KeyguardHandler {
        /** Whether we are currently on the keyguard or not. */
        fun isOnKeyguard(): Boolean

        /** Hide the keyguard and animate using [runner]. */
        fun hideKeyguardWithAnimation(runner: IRemoteAnimationRunner)
    }

    /**
     * A controller that takes care of applying the animation to an expanding view.
     *
@@ -337,17 +365,17 @@ class ActivityLaunchAnimator(context: Context) {

        override fun onAnimationStart(
            @WindowManager.TransitionOldType transit: Int,
            remoteAnimationTargets: Array<out RemoteAnimationTarget>,
            remoteAnimationWallpaperTargets: Array<out RemoteAnimationTarget>,
            remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
            iRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback
            apps: Array<out RemoteAnimationTarget>?,
            wallpapers: Array<out RemoteAnimationTarget>?,
            nonApps: Array<out RemoteAnimationTarget>?,
            iCallback: IRemoteAnimationFinishedCallback?
        ) {
            removeTimeout()

            // The animation was started too late and we already notified the controller that it
            // timed out.
            if (timedOut) {
                invokeCallback(iRemoteAnimationFinishedCallback)
                iCallback?.invoke()
                return
            }

@@ -358,30 +386,29 @@ class ActivityLaunchAnimator(context: Context) {
            }

            context.mainExecutor.execute {
                startAnimation(remoteAnimationTargets, remoteAnimationNonAppTargets,
                        iRemoteAnimationFinishedCallback)
                startAnimation(apps, nonApps, iCallback)
            }
        }

        private fun startAnimation(
            remoteAnimationTargets: Array<out RemoteAnimationTarget>,
            remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
            iCallback: IRemoteAnimationFinishedCallback
            apps: Array<out RemoteAnimationTarget>?,
            nonApps: Array<out RemoteAnimationTarget>?,
            iCallback: IRemoteAnimationFinishedCallback?
        ) {
            Log.d(TAG, "Remote animation started")
            val window = remoteAnimationTargets.firstOrNull {
            val window = apps?.firstOrNull {
                it.mode == RemoteAnimationTarget.MODE_OPENING
            }

            if (window == null) {
                Log.d(TAG, "Aborting the animation as no window is opening")
                removeTimeout()
                invokeCallback(iCallback)
                iCallback?.invoke()
                controller.onLaunchAnimationCancelled()
                return
            }

            val navigationBar = remoteAnimationNonAppTargets.firstOrNull {
            val navigationBar = nonApps?.firstOrNull {
                it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
            }

@@ -439,7 +466,7 @@ class ActivityLaunchAnimator(context: Context) {

                override fun onAnimationEnd(animation: Animator?) {
                    Log.d(TAG, "Animation ended")
                    invokeCallback(iCallback)
                    iCallback?.invoke()
                    controller.onLaunchAnimationEnd(isExpandingFullyAbove)
                }
            })
@@ -519,8 +546,11 @@ class ActivityLaunchAnimator(context: Context) {
            )

            // The scale will also be applied to the corner radius, so we divide by the scale to
            // keep the original radius.
            val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius) / scale
            // keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
            // make sure that the window does not draw itself behind the expanding view. This is
            // especially important for lock screen animations, where the window is not clipped by
            // the shade.
            val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
            val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
                .withAlpha(1f)
                .withMatrix(matrix)
@@ -585,9 +615,9 @@ class ActivityLaunchAnimator(context: Context) {
            }
        }

        private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) {
        private fun IRemoteAnimationFinishedCallback.invoke() {
            try {
                iCallback.onAnimationFinished()
                onAnimationFinished()
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ public class KeyguardService extends Service {
    /**
     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
     */
    static boolean sEnableRemoteKeyguardAnimation =
    public static boolean sEnableRemoteKeyguardAnimation =
            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);

    private final KeyguardViewMediator mKeyguardViewMediator;
+62 −19
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import android.media.SoundPool;
import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -71,6 +72,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SyncRtSurfaceTransactionApplier;
import android.view.View;
@@ -426,6 +428,11 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
     */
    private IRemoteAnimationFinishedCallback mSurfaceBehindRemoteAnimationFinishedCallback;

    /**
     * The animation runner to use for the next exit animation.
     */
    private IRemoteAnimationRunner mKeyguardExitAnimationRunner;

    private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
            new DeviceConfig.OnPropertiesChangedListener() {
            @Override
@@ -1605,6 +1612,16 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
        Trace.endSection();
    }

    /** Hide the keyguard and let {@code runner} handle the animation. */
    public void hideWithAnimation(IRemoteAnimationRunner runner) {
        if (!mShowing) {
            return;
        }

        mKeyguardExitAnimationRunner = runner;
        hideLocked();
    }

    public boolean isSecure() {
        return isSecure(KeyguardUpdateMonitor.getCurrentUser());
    }
@@ -2050,6 +2067,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
                // TODO: We should stop it early by disabling the swipe up flow. Right now swipe up
                // still completes and makes the screen blank.
                if (DEBUG) Log.d(TAG, "Split system user, quit unlocking.");
                mKeyguardExitAnimationRunner = null;
                return;
            }
            mHiding = true;
@@ -2103,9 +2121,37 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
                playSounds(false);
            }

            IRemoteAnimationRunner runner = mKeyguardExitAnimationRunner;
            mKeyguardExitAnimationRunner = null;

            if (KeyguardService.sEnableRemoteKeyguardAnimation && runner != null
                    && finishedCallback != null) {
                // Wrap finishedCallback to clean up the keyguard state once the animation is done.
                IRemoteAnimationFinishedCallback callback =
                        new IRemoteAnimationFinishedCallback() {
                            @Override
                            public void onAnimationFinished() throws RemoteException {
                                finishedCallback.onAnimationFinished();
                                onKeyguardExitFinished();
                                mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
                                        0 /* fadeoutDuration */);
                            }

                            @Override
                            public IBinder asBinder() {
                                return finishedCallback.asBinder();
                            }
                        };
                try {
                    runner.onAnimationStart(WindowManager.TRANSIT_KEYGUARD_GOING_AWAY, apps,
                            wallpapers, nonApps, callback);
                } catch (RemoteException e) {
                    Slog.w(TAG, "Failed to call onAnimationStart", e);
                }

            // When remaining on the shade, there's no need to do a fancy remote animation,
            // it will dismiss the panel in that case.
            if (KeyguardService.sEnableRemoteKeyguardAnimation
            } else if (KeyguardService.sEnableRemoteKeyguardAnimation
                    && !mStatusBarStateController.leaveOpenOnKeyguardHide()
                    && apps != null && apps.length > 0) {
                mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -2115,9 +2161,6 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
                mKeyguardUnlockAnimationControllerLazy.get().notifyStartKeyguardExitAnimation(
                        apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
            } else {
                setShowingLocked(false);
                mWakeAndUnlocking = false;
                mDismissCallbackRegistry.notifyDismissSucceeded();
                mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);

                // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
@@ -2165,16 +2208,24 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
                    });
                    anim.start();
                });
                resetKeyguardDonePendingLocked();
                mHideAnimationRun = false;
                adjustStatusBarLocked();
                sendUserPresentBroadcast();

                onKeyguardExitFinished();
            }
        }

        Trace.endSection();
    }

    private void onKeyguardExitFinished() {
        setShowingLocked(false);
        mWakeAndUnlocking = false;
        mDismissCallbackRegistry.notifyDismissSucceeded();
        resetKeyguardDonePendingLocked();
        mHideAnimationRun = false;
        adjustStatusBarLocked();
        sendUserPresentBroadcast();
    }

    /**
     * Whether we're currently animating between the keyguard and the app/launcher surface behind
     * it, or will be shortly (which happens if we started a fling to dismiss the keyguard).
@@ -2211,21 +2262,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
        // Block the panel from expanding, in case we were doing a swipe to dismiss gesture.
        mKeyguardViewControllerLazy.get().blockPanelExpansionFromCurrentTouch();
        final boolean wasShowing = mShowing;
        setShowingLocked(false);

        mWakeAndUnlocking = false;
        mDismissCallbackRegistry.notifyDismissSucceeded();
        onKeyguardExitFinished();

        if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) {
            mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
        }

        finishSurfaceBehindRemoteAnimation();

        resetKeyguardDonePendingLocked();
        mHideAnimationRun = false;
        adjustStatusBarLocked();
        sendUserPresentBroadcast();
        mSurfaceBehindRemoteAnimationRequested = false;
        mKeyguardUnlockAnimationControllerLazy.get().notifyFinishedKeyguardExitAnimation();
    }
+6 −0
Original line number Diff line number Diff line
@@ -211,6 +211,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * Called by the TouchHandler when this view is tapped. This will be called for actual taps
     * only, i.e. taps that have been filtered by the FalsingManager.
     */
    public void onTap() {}

    /** Sets the last action up time this view was touched. */
    void setLastActionUpTime(long eventTime) {
        mLastActionUpTime = eventTime;
+5 −1
Original line number Diff line number Diff line
@@ -118,7 +118,11 @@ public class ActivatableNotificationViewController

            if (ev.getAction() == MotionEvent.ACTION_UP) {
                // If this is a false tap, capture the even so it doesn't result in a click.
                return mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
                boolean falseTap = mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY);
                if (!falseTap && v instanceof ActivatableNotificationView) {
                    ((ActivatableNotificationView) v).onTap();
                }
                return falseTap;
            }
            return result;
        }
Loading