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

Commit ab9e7aeb authored by Matt Pietal's avatar Matt Pietal
Browse files

Update user switching processes

Add a new callback for UserController. If the user is secure, initiate
a call to SystemUI to lockNow(), and wait for the callback. If the
callback does not arrive, crash the system after some time.

During user switching, ensure that systemui locks the device immediately
if necessary and cancels any prior attempt to dismiss.

Also, ensure that any switch is user that occurs after WM has been
notified that SystemUI is going away is stopped.

Bug: 322157041
Test: atest KeyguardViewMediatorTest
Flag: EXEMPT bugfix
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:15c517ef199695c24560aa5f6b28c374ed111d68)
Merged-In: I32699e32fe1dbe94af2abe7e7ade5363a2b6cb55
Merged-In: I02df941d89467fc678c0749f4a2436ad8fee8b7b
Merged-In: I753e696474ae2a96ef8cecf9a05ad95e9dbcb2c3

Change-Id: I32699e32fe1dbe94af2abe7e7ade5363a2b6cb55
parent 8788c676
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -147,6 +147,15 @@ public class KeyguardManager {
     */
    public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";

    /**
     * When switching to a secure user, system server will expect a callback when the UI has
     * completed the switch.
     *
     * @hide
     */
    public static final String LOCK_ON_USER_SWITCH_CALLBACK = "onSwitchCallback";


    /**
     *
     * Password lock type, see {@link #setLock}
+42 −18
Original line number Diff line number Diff line
@@ -140,8 +140,7 @@ const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L
class KeyguardUnlockAnimationController @Inject constructor(
    private val context: Context,
    private val keyguardStateController: KeyguardStateController,
    private val
    keyguardViewMediator: Lazy<KeyguardViewMediator>,
    private val keyguardViewMediator: Lazy<KeyguardViewMediator>,
    private val keyguardViewController: KeyguardViewController,
    private val featureFlags: FeatureFlags,
    private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
@@ -202,6 +201,12 @@ class KeyguardUnlockAnimationController @Inject constructor(
     */
    var playingCannedUnlockAnimation = false

    /**
     * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once and
     * should ignore any future changes to the dismiss amount before the animation finishes.
     */
    var dismissAmountThresholdsReached = false

    /**
     * Remote callback provided by Launcher that allows us to control the Launcher's unlock
     * animation and smartspace.
@@ -698,6 +703,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
            return
        }

        if (dismissAmountThresholdsReached) {
            return
        }

        if (!keyguardStateController.isShowing) {
            return
        }
@@ -730,6 +739,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
            return
        }

        // no-op if we alreaddy reached a threshold.
        if (dismissAmountThresholdsReached) {
            return
        }

        // no-op if animation is not requested yet.
        if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
                !keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
@@ -744,6 +758,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
                        !keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
                        dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
            setSurfaceBehindAppearAmount(1f)
            dismissAmountThresholdsReached = true
            keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
                    false /* cancelled */)
        }
@@ -824,34 +839,43 @@ class KeyguardUnlockAnimationController @Inject constructor(
     * This is generally triggered by us, calling
     * [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation].
     */
    fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
    fun notifyFinishedKeyguardExitAnimation(showKeyguard: Boolean) {
        // Cancel any pending actions.
        handler.removeCallbacksAndMessages(null)

        // Make sure we made the surface behind fully visible, just in case. It should already be
        // fully visible. The exit animation is finished, and we should not hold the leash anymore,
        // so forcing it to 1f.
        // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
        if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
            lockscreenSmartspace?.visibility = View.VISIBLE
        }

        if (!showKeyguard) {
            // Make sure we made the surface behind fully visible, just in case. It should already
            // be fully visible. The exit animation is finished, and we should not hold the leash
            // anymore, so forcing it to 1f.
            surfaceBehindAlpha = 1f
            setSurfaceBehindAppearAmount(1f)
        surfaceBehindAlphaAnimator.cancel()
        surfaceBehindEntryAnimator.cancel()

            try {
                launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
            } catch (e: RemoteException) {
                Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
            }
        }

        listeners.forEach { it.onUnlockAnimationFinished() }

        // Reset all state
        surfaceBehindAlphaAnimator.cancel()
        surfaceBehindEntryAnimator.cancel()


        // That target is no longer valid since the animation finished, null it out.
        surfaceBehindRemoteAnimationTargets = null

        playingCannedUnlockAnimation = false
        dismissAmountThresholdsReached = false
        willUnlockWithInWindowLauncherAnimations = false
        willUnlockWithSmartspaceTransition = false

        // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
        lockscreenSmartspace?.visibility = View.VISIBLE

        listeners.forEach { it.onUnlockAnimationFinished() }
    }

    /**
+268 −89

File changed.

Preview size limit exceeded, changes collapsed.

+13 −20
Original line number Diff line number Diff line
@@ -228,34 +228,27 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
    }

    private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
        trace(nextStep, isManual)
        val emitted = _transitions.tryEmit(nextStep)
        if (!emitted) {
            Log.w(TAG, "Failed to emit next value without suspending")
        }
        logAndTrace(nextStep, isManual)
        _transitions.tryEmit(nextStep)
        lastStep = nextStep
    }

    private fun trace(step: TransitionStep, isManual: Boolean) {
    private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
        if (step.transitionState == TransitionState.RUNNING) {
            return
        }
        val traceName =
            "Transition: ${step.from} -> ${step.to} " +
                if (isManual) {
                    "(manual)"
                } else {
                    ""
                }
        val manualStr = if (isManual) " (manual)" else ""
        val traceName = "Transition: ${step.from} -> ${step.to}$manualStr"

        val traceCookie = traceName.hashCode()
        if (step.transitionState == TransitionState.STARTED) {
            Trace.beginAsyncSection(traceName, traceCookie)
        } else if (
            step.transitionState == TransitionState.FINISHED ||
                step.transitionState == TransitionState.CANCELED
        ) {
            Trace.endAsyncSection(traceName, traceCookie)
        when (step.transitionState) {
            TransitionState.STARTED -> Trace.beginAsyncSection(traceName, traceCookie)
            TransitionState.FINISHED -> Trace.endAsyncSection(traceName, traceCookie)
            TransitionState.CANCELED -> Trace.endAsyncSection(traceName, traceCookie)
            else -> {}
        }

        Log.i(TAG, "${step.transitionState.name} transition: $step$manualStr")
    }

    companion object {
+134 −3
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -56,6 +57,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardDisplayManager;
import com.android.keyguard.KeyguardSecurityView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
@@ -89,9 +91,12 @@ 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.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@@ -135,12 +140,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private @Mock AuthController mAuthController;
    private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
    private @Mock ShadeWindowLogger mShadeWindowLogger;
    private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
            mKeyguardUpdateMonitorCallbackCaptor;
    private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());

    private FalsingCollectorFake mFalsingCollector;

    private @Mock CentralSurfaces mCentralSurfaces;
    private int mInitialUserId;

    @Before
    public void setUp() throws Exception {
@@ -164,6 +172,105 @@ 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 testGoingAwayFollowedByUserSwitchDoesNotHideKeyguard() {
        setCurrentUser(/* userId= */1099, /* isSecure= */false);

        // Setup keyguard
        mViewMediator.onSystemReady();
        processAllMessagesAndBgExecutorMessages();
        mViewMediator.setShowingLocked(true);

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

        // After the request, begin a switch to a new secure user
        int nextUserId = 500;
        setCurrentUser(nextUserId, /* isSecure= */true);

        // After that request has begun, have WM tell us to exit keyguard
        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();

        // The call to exit should be rejected, and keyguard should still be visible
        verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation(
                any(), anyLong(), anyBoolean());
        assertTrue(mViewMediator.isShowingAndNotOccluded());
    }

    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() {
        setCurrentUser(/* userId= */1099, /* isSecure= */true);

        // Setup keyguard as not visible
        mViewMediator.onSystemReady();
        processAllMessagesAndBgExecutorMessages();
        mViewMediator.setShowingLocked(false);
        processAllMessagesAndBgExecutorMessages();

        // Begin a switch to a new secure user
        int nextUserId = 500;
        setCurrentUser(nextUserId, /* isSecure= */true);

        assertTrue(mViewMediator.isShowingAndNotOccluded());
    }

    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
        // GIVEN keyguard is not enabled and isn't showing
        mViewMediator.onSystemReady();
        mViewMediator.setKeyguardEnabled(false);
        TestableLooper.get(this).processAllMessages();
        captureKeyguardUpdateMonitorCallback();
        assertFalse(mViewMediator.isShowingAndNotOccluded());

        // WHEN lockdown occurs
        when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true);
        mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0);

        // THEN keyguard is shown
        TestableLooper.get(this).processAllMessages();
        assertTrue(mViewMediator.isShowingAndNotOccluded());
    }

    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    public void doNotHideKeyguard_whenLockdown_onKeyguardNotEnabledExternally() {
        // GIVEN keyguard is enabled and lockdown occurred so the keyguard is showing
        mViewMediator.onSystemReady();
        mViewMediator.setKeyguardEnabled(true);
        TestableLooper.get(this).processAllMessages();
        captureKeyguardUpdateMonitorCallback();
        when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true);
        mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0);
        assertTrue(mViewMediator.isShowingAndNotOccluded());

        // WHEN keyguard is externally not enabled anymore
        mViewMediator.setKeyguardEnabled(false);

        // THEN keyguard is NOT dismissed; it continues to show
        TestableLooper.get(this).processAllMessages();
        assertTrue(mViewMediator.isShowingAndNotOccluded());
    }

    @Test
@@ -361,6 +468,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    @Test
    @TestableLooper.RunWithLooper(setAsMainLooper = true)
    public void testCancelKeyguardExitAnimation_noPendingLock_keyguardWillNotBeShowing() {
        when(mPowerManager.isInteractive()).thenReturn(true);

        setCurrentUser(0, false);
        startMockKeyguardExitAnimation();
        cancelMockKeyguardExitAnimation();

@@ -412,12 +522,21 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
        assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
    }

    /**
     * Interactions with the ActivityTaskManagerService and others are posted to an executor that
     * doesn't use the testable looper. Use this method to ensure those are run as well.
     */
    private void processAllMessagesAndBgExecutorMessages() {
        TestableLooper.get(this).processAllMessages();
        mUiBgExecutor.runAllReady();
    }

    /**
     * Configures mocks appropriately, then starts the keyguard exit animation.
     */
    private void startMockKeyguardExitAnimation() {
        mViewMediator.onSystemReady();
        TestableLooper.get(this).processAllMessages();
        processAllMessagesAndBgExecutorMessages();

        mViewMediator.setShowingLocked(true);

@@ -430,9 +549,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
        IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);

        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
        mViewMediator.mKeyguardGoingAwayRunnable.run();
        mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
                null, callback);
        TestableLooper.get(this).processAllMessages();
        processAllMessagesAndBgExecutorMessages();
    }

    /**
@@ -441,7 +561,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private void cancelMockKeyguardExitAnimation() {
        when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
        mViewMediator.cancelKeyguardExitAnimation();
        TestableLooper.get(this).processAllMessages();
        processAllMessagesAndBgExecutorMessages();
    }

    @Test
@@ -550,4 +670,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {

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

    private void captureKeyguardUpdateMonitorCallback() {
        verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
    }

    private void setCurrentUser(int userId, boolean isSecure) {
        when(mUserTracker.getUserId()).thenReturn(userId);
        when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure);
        mViewMediator.setCurrentUser(userId);
        processAllMessagesAndBgExecutorMessages();
    }
}
Loading