[coastguard skipped] Merge sparse cherrypicks from sparse-11973172-L93500030004728592 into 24Q2-release.
COASTGUARD_SKIP: I0bcd3ba4ad5ee89653447e8119868deec2175a07
COASTGUARD_SKIP: I37b98d23fc46de6eaafef85fc76bbde76ed7c4bf
Change-Id: I18d62c57b9289506673b65507d98ceadb91555ec
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a7472d8..3abfe082 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -154,6 +154,9 @@
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;
@@ -220,18 +223,9 @@
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;
@@ -1828,8 +1822,15 @@
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 {
@@ -2340,56 +2341,34 @@
@VisibleForTesting
void completeUserSwitch(int oldUserId, int newUserId) {
- 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));
- }
+ 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
+ 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);
} else {
- sendUserSwitchCompleteMessage.run();
+ 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) {
boolean homeInFront = mInjector.taskSupervisorSwitchUser(newUserId, uss);
if (homeInFront) {
@@ -3826,45 +3805,29 @@
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
- protected void showKeyguard(Runnable runnable) {
- if (getWindowManager().isKeyguardLocked()) {
- runnable.run();
- return;
- }
- getActivityTaskManagerInternal().registerScreenObserver(
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- getActivityTaskManagerInternal().unregisterScreenObserver(this);
- runnable.run();
- }
- }
- }
- );
- getWindowManager().lockDeviceNow();
- }
-
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 {
- runnable.run();
+ mHandler.post(runOnce);
}
@Override
public void onDismissSucceeded() throws RemoteException {
- runnable.run();
+ mHandler.post(runOnce);
}
@Override
public void onDismissCancelled() throws RemoteException {
- runnable.run();
+ mHandler.post(runOnce);
}
}, /* message= */ null);
}
@@ -3890,5 +3853,43 @@
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();
+ }
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index eb795ab..cea10ea 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -57,7 +57,6 @@
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;
@@ -90,7 +89,6 @@
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;
@@ -200,10 +198,7 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
- doAnswer(invocation -> {
- ((Runnable) invocation.getArgument(0)).run();
- return null;
- }).when(mInjector).showKeyguard(any());
+ doNothing().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
mockIsUsersOnSecondaryDisplaysEnabled(false);
// All UserController params are set to default.
@@ -535,6 +530,7 @@
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);
@@ -1051,13 +1047,21 @@
// mock the device to be secure in order to expect the keyguard to be shown
when(mInjector.mKeyguardManagerMock.isDeviceSecure(anyInt())).thenReturn(true);
- // call real showKeyguard method for this test
- doCallRealMethod().when(mInjector).showKeyguard(any());
+ // call real lockDeviceNowAndWaitForKeyguardShown method for this test
+ doCallRealMethod().when(mInjector).lockDeviceNowAndWaitForKeyguardShown();
- mUserController.completeUserSwitch(TEST_USER_ID1, TEST_USER_ID2);
+ // 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();
- // make sure the switch is stalled by checking the UserSwitchingDialog is not dismissed yet
- verify(mInjector, never()).dismissUserSwitchingDialog(any());
+ // 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());
// mock send the keyguard shown event
ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
@@ -1065,42 +1069,12 @@
verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
captor.getValue().onKeyguardStateChanged(true);
- // 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,
- /* backgroundUserScheduledStopTimeSecs= */ -1);
-
- // 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());
+ // 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());
}
private void setUpAndStartUserInBackground(int userId) throws Exception {
@@ -1436,9 +1410,7 @@
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);
- }
+ result.add(msg.what);
}
return result;
}
@@ -1462,28 +1434,14 @@
@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);
- 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();
- }
+ if (msg.getCallback() != null) {
+ msg.getCallback().run();
+ msg.setCallback(null);
}
+ return super.sendMessageAtTime(msg, uptimeMillis);
}
}
}