Loading services/core/java/com/android/server/am/UserController.java +83 −84 Original line number Diff line number Diff line Loading @@ -154,9 +154,6 @@ import java.util.Arrays; import java.util.Iterator; 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; Loading Loading @@ -222,10 +219,19 @@ class UserController implements Handler.Callback { */ private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000; /** * Amount of time waited for * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be * called after calling {@link WindowManagerService#lockDeviceNow}. * Otherwise, we should throw a {@link RuntimeException} and never dismiss the * {@link UserSwitchingDialog}. */ static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 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()} * 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; Loading Loading @@ -1822,15 +1828,8 @@ class UserController implements Handler.Callback { updateProfileRelatedCaches(); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device // with the option to show the user switcher on the keyguard. if (userSwitchUiEnabled) { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { // Make sure the device is locked before moving on with the user switch mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } } else { Loading Loading @@ -2341,32 +2340,54 @@ 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 sendUserSwitchCompleteMessage = () -> { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage( REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); }; if (isUserSwitchUiEnabled()) { if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); } else { this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); } } else { sendUserSwitchCompleteMessage.run(); } )); } private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) { if (condition) { conditionalStep.accept(nextStep); } else { nextStep.run(); protected void showKeyguard(Runnable runnable) { runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> { throw new RuntimeException( "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms."); }, "showKeyguard"); } protected void dismissKeyguard(Runnable runnable) { runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable, "dismissKeyguard"); } private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess, Runnable onTimeout, String traceMsg) { final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING) asyncTraceBegin(traceMsg, 0); mHandler.postDelayed(() -> { if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT) asyncTraceEnd(traceMsg, 0); Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs); onTimeout.run(); } }, timeoutMs); task.accept(() -> { if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS) asyncTraceEnd(traceMsg, 0); onSuccess.run(); } }); } private void moveUserToForeground(UserState uss, int newUserId) { Loading Loading @@ -3805,29 +3826,45 @@ 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)) { protected void showKeyguard(Runnable runnable) { if (getWindowManager().isKeyguardLocked()) { runnable.run(); return; } }; getActivityTaskManagerInternal().registerScreenObserver( new ActivityTaskManagerInternal.ScreenObserver() { @Override public void onAwakeStateChanged(boolean isAwake) { mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS); } @Override public void onKeyguardStateChanged(boolean isShowing) { if (isShowing) { getActivityTaskManagerInternal().unregisterScreenObserver(this); runnable.run(); } } } ); getWindowManager().lockDeviceNow(); } protected void dismissKeyguard(Runnable runnable) { getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { mHandler.post(runOnce); runnable.run(); } @Override public void onDismissSucceeded() throws RemoteException { mHandler.post(runOnce); runnable.run(); } @Override public void onDismissCancelled() throws RemoteException { mHandler.post(runOnce); runnable.run(); } }, /* message= */ null); } Loading @@ -3853,43 +3890,5 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } 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) { latch.countDown(); } } }; getActivityTaskManagerInternal().registerScreenObserver(screenObserver); getWindowManager().lockDeviceNow(); try { if (!latch.await(20, TimeUnit.SECONDS)) { throw new RuntimeException("Keyguard is not shown in 20 seconds"); } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); t.traceEnd(); } } } } services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +67 −26 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; Loading Loading @@ -89,6 +90,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; Loading Loading @@ -198,7 +200,10 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mInjector).showKeyguard(any()); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. Loading Loading @@ -530,7 +535,6 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); expectedCodes.add(0); // this is for directly posting in stopping. } Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); assertEquals("Unexpected message sent", expectedCodes, actualCodes); Loading Loading @@ -1047,21 +1051,13 @@ public class UserControllerTest { // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); // call real lockDeviceNowAndWaitForKeyguardShown method for this test doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); // call real showKeyguard method for this test doCallRealMethod().when(mInjector).showKeyguard(any()); // call startUser on a thread because we're expecting it to be blocked Thread threadStartUser = new Thread(()-> { mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); }); threadStartUser.start(); mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); // make sure the switch is stalled... Thread.sleep(2000); // by checking REPORT_USER_SWITCH_MSG is not sent yet assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); // and the thread is still alive assertTrue(threadStartUser.isAlive()); // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet verify(mInjector, never()).dismissUserSwitchingDialog(any()); // mock send the keyguard shown event ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( Loading @@ -1069,12 +1065,41 @@ public class UserControllerTest { verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); captor.getValue().onKeyguardStateChanged(true); // verify the switch now moves on... Thread.sleep(1000); // by checking REPORT_USER_SWITCH_MSG is sent assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); // and the thread is finished assertFalse(threadStartUser.isAlive()); // verify the switch now moves on by checking the UserSwitchingDialog is dismissed verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any()); // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system try { mInjector.mHandler.processPostDelayedCallbacksWithin( UserController.SHOW_KEYGUARD_TIMEOUT_MS); } catch (RuntimeException e) { throw new AssertionError( "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e); } } @Test public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception { // enable user switch ui, because keyguard is only shown then mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); // suppress showKeyguard method for this test doNothing().when(mInjector).showKeyguard(any()); mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); // verify that the system has crashed assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> { mInjector.mHandler.processPostDelayedCallbacksWithin( UserController.SHOW_KEYGUARD_TIMEOUT_MS); }); // make sure the UserSwitchingDialog is not dismissed verify(mInjector, never()).dismissUserSwitchingDialog(any()); } private void setUpAndStartUserInBackground(int userId) throws Exception { Loading Loading @@ -1410,8 +1435,10 @@ public class UserControllerTest { Set<Integer> getMessageCodes() { Set<Integer> result = new LinkedHashSet<>(); for (Message msg : mMessages) { if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages result.add(msg.what); } } return result; } Loading @@ -1434,14 +1461,28 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { final Runnable cb = msg.getCallback(); if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) { // run mHandler.post calls immediately cb.run(); return true; } Message copy = new Message(); copy.copyFrom(msg); copy.setCallback(cb); mMessages.add(copy); if (msg.getCallback() != null) { msg.getCallback().run(); return super.sendMessageAtTime(msg, uptimeMillis); } public void processPostDelayedCallbacksWithin(long millis) { final long whenMax = SystemClock.uptimeMillis() + millis; for (Message msg : mMessages) { final Runnable cb = msg.getCallback(); if (cb != null && msg.getWhen() <= whenMax) { msg.setCallback(null); cb.run(); } } return super.sendMessageAtTime(msg, uptimeMillis); } } } Loading
services/core/java/com/android/server/am/UserController.java +83 −84 Original line number Diff line number Diff line Loading @@ -154,9 +154,6 @@ import java.util.Arrays; import java.util.Iterator; 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; Loading Loading @@ -222,10 +219,19 @@ class UserController implements Handler.Callback { */ private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000; /** * Amount of time waited for * {@link ActivityTaskManagerInternal.ScreenObserver#onKeyguardStateChanged} callbacks to be * called after calling {@link WindowManagerService#lockDeviceNow}. * Otherwise, we should throw a {@link RuntimeException} and never dismiss the * {@link UserSwitchingDialog}. */ static final int SHOW_KEYGUARD_TIMEOUT_MS = 20 * 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()} * 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; Loading Loading @@ -1822,15 +1828,8 @@ class UserController implements Handler.Callback { updateProfileRelatedCaches(); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device // with the option to show the user switcher on the keyguard. if (userSwitchUiEnabled) { mInjector.getWindowManager().setSwitchingUser(true); // Only lock if the user has a secure keyguard PIN/Pattern/Pwd if (mInjector.getKeyguardManager().isDeviceSecure(userId)) { // Make sure the device is locked before moving on with the user switch mInjector.lockDeviceNowAndWaitForKeyguardShown(); } } } else { Loading Loading @@ -2341,32 +2340,54 @@ 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 sendUserSwitchCompleteMessage = () -> { mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); mHandler.sendMessage(mHandler.obtainMessage( REPORT_USER_SWITCH_COMPLETE_MSG, oldUserId, newUserId)); }; if (isUserSwitchUiEnabled()) { if (mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { this.showKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); } else { this.dismissKeyguard(() -> dismissUserSwitchDialog(sendUserSwitchCompleteMessage)); } } else { sendUserSwitchCompleteMessage.run(); } )); } private void await(boolean condition, Consumer<Runnable> conditionalStep, Runnable nextStep) { if (condition) { conditionalStep.accept(nextStep); } else { nextStep.run(); protected void showKeyguard(Runnable runnable) { runWithTimeout(mInjector::showKeyguard, SHOW_KEYGUARD_TIMEOUT_MS, runnable, () -> { throw new RuntimeException( "Keyguard is not shown in " + SHOW_KEYGUARD_TIMEOUT_MS + " ms."); }, "showKeyguard"); } protected void dismissKeyguard(Runnable runnable) { runWithTimeout(mInjector::dismissKeyguard, DISMISS_KEYGUARD_TIMEOUT_MS, runnable, runnable, "dismissKeyguard"); } private void runWithTimeout(Consumer<Runnable> task, int timeoutMs, Runnable onSuccess, Runnable onTimeout, String traceMsg) { final AtomicInteger state = new AtomicInteger(0); // state = 0 (RUNNING) asyncTraceBegin(traceMsg, 0); mHandler.postDelayed(() -> { if (state.compareAndSet(0, 1)) { // state = 1 (TIMEOUT) asyncTraceEnd(traceMsg, 0); Slogf.w(TAG, "Timeout: %s did not finish in %d ms", traceMsg, timeoutMs); onTimeout.run(); } }, timeoutMs); task.accept(() -> { if (state.compareAndSet(0, 2)) { // state = 2 (SUCCESS) asyncTraceEnd(traceMsg, 0); onSuccess.run(); } }); } private void moveUserToForeground(UserState uss, int newUserId) { Loading Loading @@ -3805,29 +3826,45 @@ 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)) { protected void showKeyguard(Runnable runnable) { if (getWindowManager().isKeyguardLocked()) { runnable.run(); return; } }; getActivityTaskManagerInternal().registerScreenObserver( new ActivityTaskManagerInternal.ScreenObserver() { @Override public void onAwakeStateChanged(boolean isAwake) { mHandler.postDelayed(runOnce, DISMISS_KEYGUARD_TIMEOUT_MS); } @Override public void onKeyguardStateChanged(boolean isShowing) { if (isShowing) { getActivityTaskManagerInternal().unregisterScreenObserver(this); runnable.run(); } } } ); getWindowManager().lockDeviceNow(); } protected void dismissKeyguard(Runnable runnable) { getWindowManager().dismissKeyguard(new IKeyguardDismissCallback.Stub() { @Override public void onDismissError() throws RemoteException { mHandler.post(runOnce); runnable.run(); } @Override public void onDismissSucceeded() throws RemoteException { mHandler.post(runOnce); runnable.run(); } @Override public void onDismissCancelled() throws RemoteException { mHandler.post(runOnce); runnable.run(); } }, /* message= */ null); } Loading @@ -3853,43 +3890,5 @@ class UserController implements Handler.Callback { void onSystemUserVisibilityChanged(boolean visible) { getUserManagerInternal().onSystemUserVisibilityChanged(visible); } 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) { latch.countDown(); } } }; getActivityTaskManagerInternal().registerScreenObserver(screenObserver); getWindowManager().lockDeviceNow(); try { if (!latch.await(20, TimeUnit.SECONDS)) { throw new RuntimeException("Keyguard is not shown in 20 seconds"); } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver); t.traceEnd(); } } } }
services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +67 −26 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; Loading Loading @@ -89,6 +90,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; Loading Loading @@ -198,7 +200,10 @@ public class UserControllerTest { doNothing().when(mInjector).activityManagerOnUserStopped(anyInt()); doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt()); doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt()); doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; }).when(mInjector).showKeyguard(any()); mockIsUsersOnSecondaryDisplaysEnabled(false); // All UserController params are set to default. Loading Loading @@ -530,7 +535,6 @@ public class UserControllerTest { expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG); if (backgroundUserStopping) { expectedCodes.add(CLEAR_USER_JOURNEY_SESSION_MSG); expectedCodes.add(0); // this is for directly posting in stopping. } Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); assertEquals("Unexpected message sent", expectedCodes, actualCodes); Loading Loading @@ -1047,21 +1051,13 @@ public class UserControllerTest { // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); // call real lockDeviceNowAndWaitForKeyguardShown method for this test doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown(); // call real showKeyguard method for this test doCallRealMethod().when(mInjector).showKeyguard(any()); // call startUser on a thread because we're expecting it to be blocked Thread threadStartUser = new Thread(()-> { mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND); }); threadStartUser.start(); mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); // make sure the switch is stalled... Thread.sleep(2000); // by checking REPORT_USER_SWITCH_MSG is not sent yet assertNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); // and the thread is still alive assertTrue(threadStartUser.isAlive()); // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet verify(mInjector, never()).dismissUserSwitchingDialog(any()); // mock send the keyguard shown event ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass( Loading @@ -1069,12 +1065,41 @@ public class UserControllerTest { verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture()); captor.getValue().onKeyguardStateChanged(true); // verify the switch now moves on... Thread.sleep(1000); // by checking REPORT_USER_SWITCH_MSG is sent assertNotNull(mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG)); // and the thread is finished assertFalse(threadStartUser.isAlive()); // verify the switch now moves on by checking the UserSwitchingDialog is dismissed verify(mInjector, atLeastOnce()).dismissUserSwitchingDialog(any()); // verify that SHOW_KEYGUARD_TIMEOUT is ignored and does not crash the system try { mInjector.mHandler.processPostDelayedCallbacksWithin( UserController.SHOW_KEYGUARD_TIMEOUT_MS); } catch (RuntimeException e) { throw new AssertionError( "SHOW_KEYGUARD_TIMEOUT is not ignored and crashed the system", e); } } @Test public void testRuntimeExceptionIsThrownIfTheKeyguardIsNotShown() throws Exception { // enable user switch ui, because keyguard is only shown then mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); // mock the device to be secure in order to expect the keyguard to be shown when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true); // suppress showKeyguard method for this test doNothing().when(mInjector).showKeyguard(any()); mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2); // verify that the system has crashed assertThrows("Should have thrown RuntimeException", RuntimeException.class, () -> { mInjector.mHandler.processPostDelayedCallbacksWithin( UserController.SHOW_KEYGUARD_TIMEOUT_MS); }); // make sure the UserSwitchingDialog is not dismissed verify(mInjector, never()).dismissUserSwitchingDialog(any()); } private void setUpAndStartUserInBackground(int userId) throws Exception { Loading Loading @@ -1410,8 +1435,10 @@ public class UserControllerTest { Set<Integer> getMessageCodes() { Set<Integer> result = new LinkedHashSet<>(); for (Message msg : mMessages) { if (msg.what != 0) { // ignore mHandle.post and mHandler.postDelayed messages result.add(msg.what); } } return result; } Loading @@ -1434,14 +1461,28 @@ public class UserControllerTest { @Override public boolean sendMessageAtTime(Message msg, long uptimeMillis) { final Runnable cb = msg.getCallback(); if (cb != null && uptimeMillis <= SystemClock.uptimeMillis()) { // run mHandler.post calls immediately cb.run(); return true; } Message copy = new Message(); copy.copyFrom(msg); copy.setCallback(cb); mMessages.add(copy); if (msg.getCallback() != null) { msg.getCallback().run(); return super.sendMessageAtTime(msg, uptimeMillis); } public void processPostDelayedCallbacksWithin(long millis) { final long whenMax = SystemClock.uptimeMillis() + millis; for (Message msg : mMessages) { final Runnable cb = msg.getCallback(); if (cb != null && msg.getWhen() <= whenMax) { msg.setCallback(null); cb.run(); } } return super.sendMessageAtTime(msg, uptimeMillis); } } }