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 f17b66e731224605373efc27c448a048e954d663)
Cherrypick-From: https://googleplex-android-review.googlesource.com/q/commit:4a17a272b6eacdb5f62bc3510f6109524eeaab6e
Merged-In: Ib262129422e6198f019482547a6b129cc60f0865
Change-Id: Ib262129422e6198f019482547a6b129cc60f0865
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3e3a758..7e8a3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2431,6 +2431,7 @@
mHandler.removeCallbacksAndMessages(mDismissToken);
mHandler.removeMessages(DISMISS);
mHandler.removeMessages(HIDE);
+ mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM);
notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId));
resetKeyguardDonePendingLocked();
adjustStatusBarLocked();
@@ -3251,6 +3252,12 @@
}
}
+ // 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.
*
@@ -3282,13 +3289,7 @@
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()
@@ -3302,6 +3303,13 @@
return;
}
+ if (mIsKeyguardExitAnimationCanceled) {
+ Log.d(TAG, "Ignoring exitKeyguardAndFinishSurfaceBehindRemoteAnimation. "
+ + "mIsKeyguardExitAnimationCanceled==true");
+ finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
+ setShowingLocked(true /* showing */, true /* force */);
+ return;
+ }
onKeyguardExitFinished();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 9d7bef5..7af195d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -38,6 +38,7 @@
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;
@@ -176,12 +177,16 @@
private FakeFeatureFlags mFeatureFlags;
private int mInitialUserId;
+ private final int mDefaultUserId = 100;
+ 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);
@@ -243,6 +248,10 @@
null, callback);
processAllMessagesAndBgExecutorMessages();
+ // Followed by a request to dismiss the keyguard completely, which needs to be rejected
+ mViewMediator.mViewMediatorCallback.keyguardDonePending(true, mDefaultUserId);
+ mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+
// The call to exit should be rejected, and keyguard should still be visible
verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation(
any(), any(), anyLong(), anyBoolean());
@@ -251,6 +260,54 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testGoingAwayFollowedByBeforeUserSwitchWithDelayedExitAnimationDoesNotHideKeyguard() {
+ mUsePostAfterTraversalRunnable = true;
+
+ int insecureUserId = 1099;
+ setCurrentUser(/* userId= */insecureUserId, /* isSecure= */false);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ 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);
+ processAllMessagesAndBgExecutorMessages();
+
+ // 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);
+
+ processAllMessagesAndBgExecutorMessages();
+
+ // 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
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() {
setCurrentUser(/* userId= */1099, /* isSecure= */true);
@@ -354,7 +411,8 @@
@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());
}
@@ -978,7 +1036,16 @@
mFeatureFlags,
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
- mSystemPropertiesHelper);
+ mSystemPropertiesHelper) {
+ @Override
+ void postAfterTraversal(Runnable runnable) {
+ if (mUsePostAfterTraversalRunnable) {
+ mPostAfterTraversalRunnable = runnable;
+ } else {
+ super.postAfterTraversal(runnable);
+ }
+ }
+ };
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);