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

Commit c139c7cb authored by Matt Pietal's avatar Matt Pietal
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
Change-Id: Ib262129422e6198f019482547a6b129cc60f0865
Merged-In: Ib262129422e6198f019482547a6b129cc60f0865
(cherry picked from commit f17b66e7)
parent 0f8c8d2a
Loading
Loading
Loading
Loading
+16 −7
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import android.view.animation.AnimationUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -2049,6 +2050,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
                KeyguardUpdateMonitor.setCurrentUser(newUserId);
                mHandler.removeMessages(DISMISS);
                mHandler.removeMessages(HIDE);
                mHandler.removeMessages(START_KEYGUARD_EXIT_ANIM);
                notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(newUserId));
                resetKeyguardDonePendingLocked();
                adjustStatusBarLocked();
@@ -2804,6 +2806,12 @@ public class KeyguardViewMediator extends CoreStartable implements 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.
     *
@@ -2834,13 +2842,7 @@ public class KeyguardViewMediator extends CoreStartable implements 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()
@@ -2854,6 +2856,13 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,

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

            onKeyguardExitFinished();

+92 −7
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.keyguard;

import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;

import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -30,6 +31,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;

@@ -40,6 +42,10 @@ import android.os.PowerManager.WakeLock;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.ViewRootImpl;

import androidx.test.filters.SmallTest;

@@ -70,6 +76,7 @@ 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;
@@ -77,8 +84,6 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import dagger.Lazy;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -105,7 +110,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private @Mock ScreenOffAnimationController mScreenOffAnimationController;
    private @Mock InteractionJankMonitor mInteractionJankMonitor;
    private @Mock ScreenOnCoordinator mScreenOnCoordinator;
    private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
    private @Mock NotificationShadeWindowController mNotificationShadeWindowController;
    private @Mock DreamOverlayStateController mDreamOverlayStateController;
    private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
    private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
@@ -114,18 +119,88 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private FalsingCollectorFake mFalsingCollector;

    private @Mock CentralSurfaces mCentralSurfaces;
    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);
        when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
        final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
        when(testViewRoot.getView()).thenReturn(mock(View.class));
        when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);

        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();

        ArgumentCaptor<Runnable> runnable = ArgumentCaptor.forClass(Runnable.class);
        verify(mNotificationShadeWindowController).batchApplyWindowLayoutParams(
                runnable.capture());
        runnable.getValue().run();

        // 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
@@ -137,7 +212,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());
    }

@@ -292,8 +368,17 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
                mScreenOnCoordinator,
                mInteractionJankMonitor,
                mDreamOverlayStateController,
                mNotificationShadeWindowControllerLazy,
                () -> mActivityLaunchAnimator);
                () -> mNotificationShadeWindowController,
                () -> mActivityLaunchAnimator) {
                    @Override
                    void postAfterTraversal(Runnable runnable) {
                        if (mUsePostAfterTraversalRunnable) {
                            mPostAfterTraversalRunnable = runnable;
                        } else {
                            super.postAfterTraversal(runnable);
                        }
                    }
            };
        mViewMediator.start();

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