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

Commit b22cce5d authored by Matt Pietal's avatar Matt Pietal Committed by Mohammed Althaf T
Browse files

Ensure exit animations are canceled prior to user switch

On user switch, remove any pending requests and set a flag to indicate
a cancelation, and verify that flag just prior to handling the
exit animation callback from window manager.

Bug: 407562568
Test: atest KeyguardViewMediatorTest
Flag: EXEMPT bugfix
(cherry picked from commit f17b66e7)
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:12238e8fc6e8bfc2c4ae6100fefb0cf99ec17a76
Merged-In: Ib262129422e6198f019482547a6b129cc60f0865
Change-Id: Ib262129422e6198f019482547a6b129cc60f0865
parent 1c1b6b62
Loading
Loading
Loading
Loading
+15 −7
Original line number Diff line number Diff line
@@ -2199,6 +2199,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
                KeyguardUpdateMonitor.setCurrentUser(newUserId);
                mHandler.removeMessages(DISMISS);
                mHandler.removeMessages(HIDE);
                mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM);
                notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId));
                resetKeyguardDonePendingLocked();
                adjustStatusBarLocked();
@@ -2971,6 +2972,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
        }
    }

    // Allows the runnable to be controlled for tests by overriding this method
    @VisibleForTesting
    void postAfterTraversal(Runnable runnable) {
        DejankUtils.postAfterTraversal(runnable);
    }

    /**
     * Called when we're done running the keyguard exit animation, we should now end up unlocked.
     *
@@ -3001,13 +3008,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
        InteractionJankMonitor.getInstance().end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);

        // Post layout changes to the next frame, so we don't hang at the end of the animation.
        DejankUtils.postAfterTraversal(() -> {
            if (mIsKeyguardExitAnimationCanceled) {
                Log.d(TAG, "Ignoring dejank exitKeyguardAndFinishSurfaceBehindRemoteAnimation. "
                      + "mIsKeyguardExitAnimationCanceled==true");
                return;
            }

        postAfterTraversal(() -> {
            if (!mPM.isInteractive() && !mPendingLock) {
                Log.e(TAG, "exitKeyguardAndFinishSurfaceBehindRemoteAnimation#postAfterTraversal:"
                        + " mPM.isInteractive()=" + mPM.isInteractive()
@@ -3021,6 +3022,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,

                return;
            }
            if (mIsKeyguardExitAnimationCanceled) {
                Log.d(TAG, "Ignoring exitKeyguardAndFinishSurfaceBehindRemoteAnimation. "
                        + "mIsKeyguardExitAnimationCanceled==true");
                finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
                setShowingLocked(true /* showing */, true /* force */);
                return;
            }

            onKeyguardExitFinished();

+85 −3
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -89,9 +90,11 @@ import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@@ -141,12 +144,16 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private FalsingCollectorFake mFalsingCollector;

    private @Mock CentralSurfaces mCentralSurfaces;
    private int mInitialUserId;
    private boolean mUsePostAfterTraversalRunnable;
    private Runnable mPostAfterTraversalRunnable;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mFalsingCollector = new FalsingCollectorFake();

        mUsePostAfterTraversalRunnable = false;
        mPostAfterTraversalRunnable = null;
        when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
        when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
        when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
@@ -164,6 +171,63 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
        DejankUtils.setImmediate(true);

        createAndStartViewMediator();
        mInitialUserId = KeyguardUpdateMonitor.getCurrentUser();
    }

    @After
    public void teardown() {
        KeyguardUpdateMonitor.setCurrentUser(mInitialUserId);
    }

    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    public void testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard() {
        mUsePostAfterTraversalRunnable = true;

        int insecureUserId = 1099;
        setCurrentUser(/* userId= */insecureUserId, /* isSecure= */false);

        // Setup keyguard
        mViewMediator.onSystemReady();
        TestableLooper.get(this).processAllMessages();
        mUiBgExecutor.runAllReady();
        mViewMediator.setShowingLocked(true);

        // Request keyguard going away
        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
        mViewMediator.showSurfaceBehindKeyguard();

        // WM will have started the exit animation...
        RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
                mock(RemoteAnimationTarget.class)
        };
        RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
                mock(RemoteAnimationTarget.class)
        };
        IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
        mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
                null, callback);
        TestableLooper.get(this).processAllMessages();
        mUiBgExecutor.runAllReady();

        // Followed by a request to dismiss the keyguard completely
        mViewMediator.mViewMediatorCallback.keyguardDonePending(true, insecureUserId);
        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();

        // ...but while the exit animation is running, a user switch comes in
        int nextUserId = 500;
        setCurrentUser(nextUserId, /* isSecure= */true);

        TestableLooper.get(this).processAllMessages();
        mUiBgExecutor.runAllReady();

        // This simulates the race condition in DejankUtils.postAfterTraversal()
        mPostAfterTraversalRunnable.run();

        // At this point, the exit animation should have been canceled, with a true value
        // indicating that keyguard will be showing
        verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(eq(true));
        assertTrue(mViewMediator.isShowingAndNotOccluded());
    }

    @Test
@@ -214,7 +278,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {

    @Test
    public void testRegisterDumpable() {
        verify(mDumpManager).registerDumpable(KeyguardViewMediator.class.getName(), mViewMediator);
        verify(mDumpManager).registerDumpable(startsWith(KeyguardViewMediator.class.getName()),
                eq(mViewMediator));
        verify(mStatusBarKeyguardViewManager, never()).setKeyguardGoingAwayState(anyBoolean());
    }

@@ -570,6 +635,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {

        onHideAnimationFinished.getValue().run();


        // This is executed when the user is incorrect
        verify(mShadeController).instantCollapseShade();
    }
@@ -604,9 +670,25 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
                () -> mShadeController,
                () -> mNotificationShadeWindowController,
                () -> mActivityLaunchAnimator,
                () -> mScrimController);
                () -> mScrimController) {
                    @Override
                    void postAfterTraversal(Runnable runnable) {
                        if (mUsePostAfterTraversalRunnable) {
                            mPostAfterTraversalRunnable = runnable;
                        } else {
                            super.postAfterTraversal(runnable);
                        }
                    }
            };
        mViewMediator.start();

        mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
    }

    private void setCurrentUser(int userId, boolean isSecure) {
        when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure);
        mViewMediator.setCurrentUser(userId);
        TestableLooper.get(this).processAllMessages();
        mUiBgExecutor.runAllReady();
    }
}