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

Commit 861a44ba authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Ensure the keyguard is force-shown after a power off/on during unlock.

This fixes b/298450484, where unlocking an insecure keyguard and then near-instantly pressing the power button twice results in showing the lockscreen over the app/launcher. The race condition requires the device to be turned off and then back on within the time it takes the asynchronous startKeyguardExitAnimation call to return. This is easiest to reproduce if the double-tap camera launch gesture is disabled.

This bug was introduced by ag/24548147, which fixed a regression where the device would remain unlocked indefinitely after locking during unlock from the bouncer. That fix worked by checking isInteractive() and refusing to (incorrectly) set the keyguard to unlocked if the device was going back to sleep during an unlock.

Unfortunately, this CL's bug is caused by the fact that the keyguard is (correctly) not set to unlocked when it's locked during unlock. We have code that notices if the keyguard states don't match, and forces a call to setShowing(force=true) to rationalize these states. In this case, the fix resulted in us thinking that we were correctly already showing the keyguard. We actually are showing the keyguard UI, but we needed to tell WM again since the keyguardGoingAway call resulted in WM showing the app/launcher surface.

This CL keeps the fix from ag/24548147, and adds three additional checks that in theory, only skip short-circuit code and force the correct keyguard state:
- in doKeyguardLocked, refuse to short-circuit if the keyguard is going away. This makes sense - going away is being cancelled if we're "doing" the keyguard (showing it).
- in handleShow, force setShowingLocked if mHiding=true (the previous condition) OR if we're going away. mHiding is actually only true between the initial call to keyguardGoingAway, and the return of the async startKeyguardExitAnimation call. During the going away animation, mHiding is false, so we don't force setShowingLocked if we re-show the keyguard during that time.
- in exitKeyguardAndFinishSurfaceBehindRemoteAnimation, add to the ag/24548147 fix by also refusing to finish unlocking if mPendingLock = true. In the bug case, pressing power twice very quickly can result in isInteractive being true (since the screen turned back on), while a lock is still pending from the first power button press. If we're about to lock the keyguard again, it makes sense to not finish exiting the keyguard.

This is, as usual, as safe a fix as they come (we're forcing setting the correct state), but of course, that's what ag/24548147 did.

Fixes: 298450484
Test: disable camera gesture AND quintuple tap to call 911, set security to swipe mode, long-press lock icon and instantly double tap power as quickly as you can
Test: unlock from bouncer to an app, sleep during app launch animation, verify device locks
Change-Id: I02a25859eec3a7fb853001463dacb3730feb9c1d
parent 70484ee7
Loading
Loading
Loading
Loading
+33 −19
Original line number Diff line number Diff line
@@ -2199,14 +2199,21 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
        // we explicitly re-set state.
        if (mShowing && mKeyguardStateController.isShowing()) {
            if (mPM.isInteractive() && !mHiding) {
                // It's already showing, and we're not trying to show it while the screen is off.
                // We can simply reset all of the views, but don't hide the bouncer in case the user
                // is currently interacting with it.
                if (DEBUG) Log.d(TAG, "doKeyguard: not showing (instead, resetting) because it is "
                        + "already showing, we're interactive, and we were not previously hiding. "
                        + "It should be safe to short-circuit here.");
                if (mKeyguardStateController.isKeyguardGoingAway()) {
                    Log.e(TAG, "doKeyguard: we're still showing, but going away. Re-show the "
                            + "keyguard rather than short-circuiting and resetting.");
                } else {
                    // It's already showing, and we're not trying to show it while the screen is
                    // off. We can simply reset all of the views, but don't hide the bouncer in case
                    // the user is currently interacting with it.
                    if (DEBUG) Log.d(TAG,
                            "doKeyguard: not showing (instead, resetting) because it is "
                                    + "already showing, we're interactive, we were not "
                                    + "previously hiding. It should be safe to short-circuit "
                                    + "here.");
                    resetStateLocked(/* hideBouncer= */ false);
                    return;
                }
            } else {
                // We are trying to show the keyguard while the screen is off or while we were in
                // the middle of hiding - this results from race conditions involving locking while
@@ -2731,13 +2738,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
            setUnlockAndWakeFromDream(false, WakeAndUnlockUpdateReason.SHOW);
            setPendingLock(false);

            // Force if we we're showing in the middle of hiding, to ensure we end up in the correct
            // state.
            setShowingLocked(true, mHiding /* force */);
            if (mHiding) {
                Log.d(TAG, "Forcing setShowingLocked because mHiding=true, which means we're "
                        + "showing in the middle of hiding.");
            final boolean hidingOrGoingAway =
                    mHiding || mKeyguardStateController.isKeyguardGoingAway();
            if (hidingOrGoingAway) {
                Log.d(TAG, "Forcing setShowingLocked because one of these is true:"
                        + "mHiding=" + mHiding
                        + ", keyguardGoingAway=" + mKeyguardStateController.isKeyguardGoingAway()
                        + ", which means we're showing in the middle of hiding.");
            }

            // Force if we we're showing in the middle of unlocking, to ensure we end up in the
            // correct state.
            setShowingLocked(true, hidingOrGoingAway /* force */);
            mHiding = false;

            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
@@ -2947,7 +2959,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
        Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
                + " fadeoutDuration=" + fadeoutDuration);
        synchronized (KeyguardViewMediator.this) {

            // Tell ActivityManager that we canceled the keyguard animation if
            // handleStartKeyguardExitAnimation was called, but we're not hiding the keyguard,
            // unless we're animating the surface behind the keyguard and will be hiding the
@@ -3215,10 +3226,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,

        // Post layout changes to the next frame, so we don't hang at the end of the animation.
        DejankUtils.postAfterTraversal(() -> {
            if (!mPM.isInteractive()) {
                Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal" +
                        "Not interactive after traversal. Don't hide the keyguard. This means we " +
                        "re-locked the device during unlock.");
            if (!mPM.isInteractive() && !mPendingLock) {
                Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal:"
                        + "mPM.isInteractive()=" + mPM.isInteractive()
                        + "mPendingLock=" + mPendingLock + "."
                        + "One of these being false means we re-locked the device during unlock. "
                        + "Do not proceed to finish keyguard exit and unlock.");
                finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
                return;
            }