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

Commit b00cff0f 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

Merged-In: I32699e32fe1dbe94af2abe7e7ade5363a2b6cb55
Merged-In: I02df941d89467fc678c0749f4a2436ad8fee8b7b

Change-Id: I753e696474ae2a96ef8cecf9a05ad95e9dbcb2c3
parent ae3d9211
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -181,6 +181,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}
+23 −18
Original line number Diff line number Diff line
@@ -939,23 +939,35 @@ 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()
        wallpaperCannedUnlockAnimator.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()
        wallpaperCannedUnlockAnimator.cancel()

        // That target is no longer valid since the animation finished, null it out.
        surfaceBehindRemoteAnimationTargets = null
@@ -965,13 +977,6 @@ class KeyguardUnlockAnimationController @Inject constructor(
        dismissAmountThresholdsReached = false
        willUnlockWithInWindowLauncherAnimations = false
        willUnlockWithSmartspaceTransition = false

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

        listeners.forEach { it.onUnlockAnimationFinished() }
    }

    /**
+237 −70

File changed.

Preview size limit exceeded, changes collapsed.

+130 −3
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.keyguard.KeyguardTransitions;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -159,6 +160,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private @Mock ShadeWindowLogger mShadeWindowLogger;
    private @Captor ArgumentCaptor<KeyguardStateController.Callback>
            mKeyguardStateControllerCallback;
    private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
            mKeyguardUpdateMonitorCallbackCaptor;
    private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
    private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());

@@ -172,6 +175,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;

    private FakeFeatureFlags mFeatureFlags;
    private int mInitialUserId;

    @Before
    public void setUp() throws Exception {
@@ -201,6 +205,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(), 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
@@ -451,6 +554,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();

@@ -502,12 +608,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);

@@ -520,9 +635,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();
    }

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

    @Test
@@ -831,4 +947,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
    private void captureKeyguardStateControllerCallback() {
        verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture());
    }

    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();
    }
}
+20 −89
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -123,7 +124,6 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
@@ -153,9 +153,7 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

/**
 * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
@@ -219,14 +217,6 @@ class UserController implements Handler.Callback {
     */
    private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;

    /**
     * Amount of time waited for {@link WindowManagerService#dismissKeyguard} callbacks to be
     * called after dismissing the keyguard.
     * Otherwise, we should move on to dismiss the dialog {@link #dismissUserSwitchDialog()}
     * and report user switch is complete {@link #REPORT_USER_SWITCH_COMPLETE_MSG}.
     */
    private static final int DISMISS_KEYGUARD_TIMEOUT_MS = 2 * 1000;

    /**
     * Time after last scheduleOnUserCompletedEvent() call at which USER_COMPLETED_EVENT_MSG will be
     * scheduled (although it may fire sooner instead).
@@ -2200,31 +2190,17 @@ class UserController implements Handler.Callback {

    @VisibleForTesting
    void completeUserSwitch(int oldUserId, int newUserId) {
        final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
        // serialize each conditional step
        await(
                // STEP 1 - If there is no challenge set, dismiss the keyguard right away
                isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId),
                mInjector::dismissKeyguard,
                () -> await(
                        // STEP 2 - If user switch ui was enabled, dismiss user switch dialog
                        isUserSwitchUiEnabled,
                        this::dismissUserSwitchDialog,
                        () -> {
                            // STEP 3 - Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast
                            // ACTION_USER_SWITCHED & call UserSwitchObservers.onUserSwitchComplete
        final Runnable runnable = () -> {
            // Send REPORT_USER_SWITCH_COMPLETE_MSG to broadcast ACTION_USER_SWITCHED and call
            // onUserSwitchComplete on UserSwitchObservers.
            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(
                    REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId));
                        }
                ));
    }

    private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) {
        if (condition) {
            conditionalStep.accept(nextStep);
        };
        if (isUserSwitchUiEnabled()) {
            dismissUserSwitchDialog(runnable);
        } else {
            nextStep.run();
            runnable.run();
        }
    }

@@ -3448,10 +3424,6 @@ class UserController implements Handler.Callback {
            return mService.mWindowManager;
        }

        ActivityTaskManagerInternal getActivityTaskManagerInternal() {
            return mService.mAtmInternal;
        }

        void activityManagerOnUserStopped(@UserIdInt int userId) {
            LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
        }
@@ -3633,33 +3605,6 @@ class UserController implements Handler.Callback {
            return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
        }

        protected void dismissKeyguard(Runnable runnable) {
            final AtomicBoolean isFirst = new AtomicBoolean(true);
            final Runnable runOnce = () -> {
                if (isFirst.getAndSet(false)) {
                    runnable.run();
                }
            };

            mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS);
            getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() {
                @Override
                public void onDismissError() throws RemoteException {
                    mHandler.post(runOnce);
                }

                @Override
                public void onDismissSucceeded() throws RemoteException {
                    mHandler.post(runOnce);
                }

                @Override
                public void onDismissCancelled() throws RemoteException {
                    mHandler.post(runOnce);
                }
            }, /* message= */ null);
        }

        boolean isHeadlessSystemUserMode() {
            return UserManager.isHeadlessSystemUserMode();
        }
@@ -3677,39 +3622,25 @@ class UserController implements Handler.Callback {
        }

        void lockDeviceNowAndWaitForKeyguardShown() {
            if (getWindowManager().isKeyguardLocked()) {
                return;
            }

            final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
            t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");

            final CountDownLatch latch = new CountDownLatch(1);
            ActivityTaskManagerInternal.ScreenObserver screenObserver =
                    new ActivityTaskManagerInternal.ScreenObserver() {
                        @Override
                        public void onAwakeStateChanged(boolean isAwake) {

                        }

                        @Override
                        public void onKeyguardStateChanged(boolean isShowing) {
                            if (isShowing) {
            Bundle bundle = new Bundle();
            bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() {
                public void sendResult(Bundle data) {
                    latch.countDown();
                }
                        }
                    };

            getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
            getWindowManager().lockDeviceNow();
            });
            getWindowManager().lockNow(bundle);
            try {
                if (!latch.await(20, TimeUnit.SECONDS)) {
                    throw new RuntimeException("Keyguard is not shown in 20 seconds");
                    throw new RuntimeException("User controller expected a callback while waiting "
                            + "to show the keyguard. Timed out after 20 seconds.");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
                t.traceEnd();
            }
        }
Loading