From 9edf758fd65ab786314ccec29fdb64f396dc0ed3 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 17 Oct 2022 14:48:09 -0700 Subject: [PATCH] Added user visibility callbacks on SystemService. On Android U, system components (like location) should rely on UM.isUserVisible() (rather than AM.getCurrentUser()) to check if a user is "visible". Hence, we need to provide a callback mechanism to notify when the user visibility changes. This change adds a SystemService.onUserVisibilityChanged() API, and also refactors how some internal components (like UserController and ActivityManagerService) handle the initial start of user 0. Finally, it also changes some user-related event logs to include more info. Bug: 244333150 Test: atest UserControllerTest # to check for breakage only Test: m update-api Test: adb shell dumpsys activity users|grep mVisibleUsers Test: adb logcat -D -b events | egrep '(I uc_|I ssm_)' Change-Id: I6898a7fc553b2bc600151715707b409b68e4880d --- .../server/activitymanagerservice.proto | 1 + .../com/android/server/SystemService.java | 12 ++ .../android/server/SystemServiceManager.java | 63 +++++++-- .../server/am/ActivityManagerService.java | 12 +- .../android/server/am/EventLogTags.logtags | 7 +- .../com/android/server/am/UserController.java | 132 +++++++++++++++--- .../android/server/am/UserControllerTest.java | 32 ++++- 7 files changed, 216 insertions(+), 43 deletions(-) diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 5099dd20a6d55..9e4f63cab86c1 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -977,6 +977,7 @@ message UserControllerProto { optional int32 profile = 2; } repeated UserProfile user_profile_group_ids = 4; + repeated int32 visible_users_array = 5; } // sync with com.android.server.am.AppTimeTracker.java diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index 933d2596aed8a..e40f001f27d5b 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -472,6 +472,18 @@ public abstract class SystemService { public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { } + /** + * The {@link UserManager#isUserVisible() user visibility} changed. + * + *

This callback is called before the user starts or is switched to (or after it stops), when + * its visibility changed because of that action. + * + * @hide + */ + // NOTE: change visible to int if this method becomes a @SystemApi + public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) { + } + /** * Called when an existing user is stopping, for system services to finalize any per-user * state they maintain for running users. This is called prior to sending the SHUTDOWN diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 1a8cf0b07cb61..83d86cdc05c6d 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -75,13 +75,17 @@ public final class SystemServiceManager implements Dumpable { // Constants used on onUser(...) // NOTE: do not change their values, as they're used on Trace calls and changes might break // performance tests that rely on them. - private static final String USER_STARTING = "Start"; // Logged as onStartUser - private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUnlockingUser - private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUnlockedUser - private static final String USER_SWITCHING = "Switch"; // Logged as onSwitchUser - private static final String USER_STOPPING = "Stop"; // Logged as onStopUser - private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser - private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onCompletedEventUser + private static final String USER_STARTING = "Start"; // Logged as onUserStarting() + private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUserUnlocking() + private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUserUnlocked() + private static final String USER_SWITCHING = "Switch"; // Logged as onUserSwitching() + private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping() + private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped() + private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent() + private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and + // onUserStarting() (when visible is true) + private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping() + // (when visibilityChanged is true) // The default number of threads to use if lifecycle thread pool is enabled. private static final int DEFAULT_MAX_USER_POOL_THREADS = 3; @@ -350,17 +354,40 @@ public final class SystemServiceManager implements Dumpable { /** * Starts the given user. */ - public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) { - EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId); + public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId, + boolean visible) { + EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0); final TargetUser targetUser = newTargetUser(userId); synchronized (mTargetUsers) { mTargetUsers.put(userId, targetUser); } + if (visible) { + // Must send the user visiiblity change first, for 2 reasons: + // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few + // services listening to this event (OTOH, there are manyy listeners to USER_STARTING + // and some can take a while to process it) + // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is + // called onUserSwitching(), so calling it before onUserStarting() make it more + // consistent with that + onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser); + } onUser(t, USER_STARTING, /* prevUser= */ null, targetUser); } + /** + * Updates the user visibility. + * + *

NOTE: this method should only be called when a user that is already running become + * visible; if the user is starting visible, callers should call + * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead + */ + public void onUserVisible(@UserIdInt int userId) { + EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId); + onUser(USER_VISIBLE, userId); + } + /** * Unlocks the given user. */ @@ -408,9 +435,12 @@ public final class SystemServiceManager implements Dumpable { /** * Stops the given user. */ - public void onUserStopping(@UserIdInt int userId) { - EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId); + public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) { + EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0); onUser(USER_STOPPING, userId); + if (visibilityChanged) { + onUser(USER_INVISIBLE, userId); + } } /** @@ -456,13 +486,12 @@ public final class SystemServiceManager implements Dumpable { TargetUser targetUser = getTargetUser(userId); Preconditions.checkState(targetUser != null, "No TargetUser for " + userId); - onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null, - targetUser); + onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null, targetUser); } private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, @Nullable TargetUser prevUser, @NonNull TargetUser curUser) { - onUser(t, onWhat, prevUser, curUser, /* completedEventType=*/ null); + onUser(t, onWhat, prevUser, curUser, /* completedEventType= */ null); } private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat, @@ -534,6 +563,12 @@ public final class SystemServiceManager implements Dumpable { threadPool.submit(getOnUserCompletedEventRunnable( t, service, serviceName, curUser, completedEventType)); break; + case USER_VISIBLE: + service.onUserVisibilityChanged(curUser, /* visible= */ true); + break; + case USER_INVISIBLE: + service.onUserVisibilityChanged(curUser, /* visible= */ false); + break; default: throw new IllegalArgumentException(onWhat + " what?"); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7e2e932f04ef3..3be1315ddf79e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -8342,14 +8342,14 @@ public class ActivityManagerService extends IActivityManager.Stub mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START, Integer.toString(currentUserId), currentUserId); - // On Automotive, at this point the system user has already been started and unlocked, - // and some of the tasks we do here have already been done. So skip those in that case. - // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the - // headless-user start logic to UserManager-land + // On Automotive / Headless System User Mode, at this point the system user has already been + // started and unlocked, and some of the tasks we do here have already been done. So skip + // those in that case. + // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user + // start logic to UserManager-land final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM; - if (bootingSystemUser) { - mSystemServiceManager.onUserStarting(t, currentUserId); + mUserController.onSystemUserStarting(); } synchronized (this) { diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags index d080036733a5f..dec8b62de2ec7 100644 --- a/services/core/java/com/android/server/am/EventLogTags.logtags +++ b/services/core/java/com/android/server/am/EventLogTags.logtags @@ -101,7 +101,7 @@ option java_package com.android.server.am 30073 uc_finish_user_stopping (userId|1|5) 30074 uc_finish_user_stopped (userId|1|5) 30075 uc_switch_user (userId|1|5) -30076 uc_start_user_internal (userId|1|5) +30076 uc_start_user_internal (userId|1|5),(foreground|1),(displayId|1|5) 30077 uc_unlock_user (userId|1|5) 30078 uc_finish_user_boot (userId|1|5) 30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5) @@ -109,13 +109,14 @@ option java_package com.android.server.am 30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3) # Tags below are used by SystemServiceManager - although it's technically part of am, these are # also user switch events and useful to be analyzed together with events above. -30082 ssm_user_starting (userId|1|5) +30082 ssm_user_starting (userId|1|5),(visible|1) 30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5) 30084 ssm_user_unlocking (userId|1|5) 30085 ssm_user_unlocked (userId|1|5) -30086 ssm_user_stopping (userId|1|5) +30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1) 30087 ssm_user_stopped (userId|1|5) 30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5) +30089 ssm_user_visible (userId|1|5) # Foreground service start/stop events. 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3) diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 3fa41c0f04209..f93dda3ff4ed3 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -100,6 +100,7 @@ import android.util.EventLog; import android.util.IntArray; import android.util.Pair; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -174,6 +175,9 @@ class UserController implements Handler.Callback { static final int START_USER_SWITCH_FG_MSG = 120; static final int COMPLETE_USER_SWITCH_MSG = 130; static final int USER_COMPLETED_EVENT_MSG = 140; + static final int USER_VISIBLE_MSG = 150; + + private static final int NO_ARG2 = 0; // Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if // the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not @@ -421,6 +425,17 @@ class UserController implements Handler.Callback { /** @see #getLastUserUnlockingUptime */ private volatile long mLastUserUnlockingUptime = 0; + /** + * List of visible users (as defined by {@link UserManager#isUserVisible()}). + * + *

It's only used to call {@link SystemServiceManager} when the visibility is changed upon + * the user starting or stopping. + * + *

Note: only the key is used, not the value. + */ + @GuardedBy("mLock") + private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray(); + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -1050,11 +1065,27 @@ class UserController implements Handler.Callback { // instead. userManagerInternal.unassignUserFromDisplay(userId); + final boolean visibilityChanged; + boolean visibleBefore; + synchronized (mLock) { + visibleBefore = mVisibleUsers.get(userId); + if (visibleBefore) { + if (DEBUG_MU) { + Slogf.d(TAG, "Removing %d from mVisibleUsers", userId); + } + mVisibleUsers.delete(userId); + visibilityChanged = true; + } else { + visibilityChanged = false; + } + } + updateStartedUserArrayLU(); final boolean allowDelayedLockingCopied = allowDelayedLocking; Runnable finishUserStoppingAsync = () -> - mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied)); + mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied, + visibilityChanged)); if (mInjector.getUserManager().isPreCreated(userId)) { finishUserStoppingAsync.run(); @@ -1092,7 +1123,7 @@ class UserController implements Handler.Callback { } private void finishUserStopping(final int userId, final UserState uss, - final boolean allowDelayedLocking) { + final boolean allowDelayedLocking, final boolean visibilityChanged) { EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId); synchronized (mLock) { if (uss.state != UserState.STATE_STOPPING) { @@ -1109,7 +1140,7 @@ class UserController implements Handler.Callback { mInjector.batteryStatsServiceNoteEvent( BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH, Integer.toString(userId), userId); - mInjector.getSystemServiceManager().onUserStopping(userId); + mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged); Runnable finishUserStoppedAsync = () -> mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking)); @@ -1513,16 +1544,17 @@ class UserController implements Handler.Callback { private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground, @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) { if (DEBUG_MU) { - Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId, + Slogf.i(TAG, "Starting user %d on display %d%s", userId, displayId, foreground ? " in foreground" : ""); } - if (displayId != Display.DEFAULT_DISPLAY) { + boolean onSecondaryDisplay = displayId != Display.DEFAULT_DISPLAY; + if (onSecondaryDisplay) { Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND " + "on secondary display (%d)", userId, displayId); } - // TODO(b/239982558): log display id (or use a new event) - EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId); + EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId, foreground ? 1 : 0, + displayId); final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); @@ -1571,8 +1603,9 @@ class UserController implements Handler.Callback { return false; } - if (foreground && userInfo.preCreated) { - Slogf.w(TAG, "Cannot start pre-created user #" + userId + " as foreground"); + if ((foreground || onSecondaryDisplay) && userInfo.preCreated) { + Slogf.w(TAG, "Cannot start pre-created user #" + userId + " in foreground or on " + + "secondary display"); return false; } @@ -1656,6 +1689,28 @@ class UserController implements Handler.Callback { } t.traceEnd(); + // Need to call UM when user is on background, as there are some cases where the user + // cannot be started in background on a secondary display (for example, if user is a + // profile). + // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as + // the UM call would return true during boot (when CarService / BootUserInitializer + // calls AM.startUserInBackground() because the system user is still the current user. + // TODO(b/244644281): another fragility of this check is that it must wait to call + // UMI.isUserVisible() until the user state is check, as that method checks if the + // profile of the current user is started. We should fix that dependency so the logic + // belongs to just one place (like UserDisplayAssigner) + boolean visible = foreground + || userId != UserHandle.USER_SYSTEM + && mInjector.getUserManagerInternal().isUserVisible(userId); + if (visible) { + synchronized (mLock) { + if (DEBUG_MU) { + Slogf.d(TAG, "Adding %d to mVisibleUsers", userId); + } + mVisibleUsers.put(userId, true); + } + } + // Make sure user is in the started state. If it is currently // stopping, we need to knock that off. if (uss.state == UserState.STATE_STOPPING) { @@ -1692,8 +1747,15 @@ class UserController implements Handler.Callback { // Booting up a new user, need to tell system services about it. // Note that this is on the same handler as scheduling of broadcasts, // which is important because it needs to go first. - mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0)); + mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, + visible ? 1 : 0)); t.traceEnd(); + } else if (visible) { + // User was already running and became visible (for example, when switching to a + // user that was started in the background before), so it's necessary to explicitly + // notify the services (while when the user starts from BOOTING, USER_START_MSG + // takes care of that. + mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2)); } t.traceBegin("sendMessages"); @@ -2110,6 +2172,11 @@ class UserController implements Handler.Callback { mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); stopUserOnSwitchIfEnforced(oldUserId); + if (oldUserId == UserHandle.USER_SYSTEM) { + // System user is never stopped, but its visibility is changed (as it is brought to the + // background) + updateSystemUserVisibility(/* visible= */ false); + } t.traceEnd(); // end continueUserSwitch } @@ -2413,9 +2480,7 @@ class UserController implements Handler.Callback { void setAllowUserUnlocking(boolean allowed) { mAllowUserUnlocking = allowed; if (DEBUG_MU) { - // TODO(b/245335748): use Slogf.d instead - // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed); - android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception()); + Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed); } } @@ -2457,10 +2522,34 @@ class UserController implements Handler.Callback { } void onSystemReady() { + if (DEBUG_MU) { + Slogf.d(TAG, "onSystemReady()"); + + } updateCurrentProfileIds(); mInjector.reportCurWakefulnessUsageEvent(); } + // TODO(b/242195409): remove this method if initial system user boot logic is refactored? + void onSystemUserStarting() { + updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode()); + } + + private void updateSystemUserVisibility(boolean visible) { + if (DEBUG_MU) { + Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible); + } + int userId = UserHandle.USER_SYSTEM; + synchronized (mLock) { + if (visible) { + mVisibleUsers.put(userId, true); + } else { + mVisibleUsers.delete(userId); + } + } + mInjector.onUserStarting(userId, visible); + } + /** * Refreshes the list of users related to the current user when either a * user switch happens or when a new related user is started in the @@ -2846,6 +2935,9 @@ class UserController implements Handler.Callback { proto.end(uToken); } } + for (int i = 0; i < mVisibleUsers.size(); i++) { + proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i)); + } proto.end(token); } } @@ -2899,7 +2991,8 @@ class UserController implements Handler.Callback { if (mSwitchingToSystemUserMessage != null) { pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage); } - pw.println(" mLastUserUnlockingUptime:" + mLastUserUnlockingUptime); + pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime); + pw.println(" mVisibleUsers: " + mVisibleUsers); } } @@ -2936,8 +3029,7 @@ class UserController implements Handler.Callback { logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER, USER_LIFECYCLE_EVENT_STATE_BEGIN); - mInjector.getSystemServiceManager().onUserStarting( - TimingsTraceAndSlog.newAsyncLog(), msg.arg1); + mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1); scheduleOnUserCompletedEvent(msg.arg1, UserCompletedEventType.EVENT_TYPE_USER_STARTING, USER_COMPLETED_EVENT_DELAY_MS); @@ -3018,6 +3110,9 @@ class UserController implements Handler.Callback { case COMPLETE_USER_SWITCH_MSG: completeUserSwitch(msg.arg1); break; + case USER_VISIBLE_MSG: + mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1); + break; } return false; } @@ -3531,5 +3626,10 @@ class UserController implements Handler.Callback { boolean isUsersOnSecondaryDisplaysEnabled() { return UserManager.isUsersOnSecondaryDisplaysEnabled(); } + + void onUserStarting(int userId, boolean visible) { + getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId, + visible); + } } } 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 0b776a3e66425..fe92a1dbdac1c 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -24,6 +24,7 @@ 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.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_SYSTEM; import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; @@ -246,7 +247,7 @@ public class UserControllerTest { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); - mUserController.startUser(TEST_USER_ID, true /* foreground */); + mUserController.startUser(TEST_USER_ID, /* foreground= */ true); verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt()); verify(mInjector.getWindowManager(), never()).stopFreezingScreen(); verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean()); @@ -258,6 +259,8 @@ public class UserControllerTest { assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true)); // Make sure no intents have been fired for pre-created users. assertTrue(mInjector.mSentIntents.isEmpty()); + + verifyUserNeverAssignedToDisplay(); } @Test @@ -280,6 +283,8 @@ public class UserControllerTest { // binder calls, but their side effects (in this case, that the user is stopped right away) assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes()) .containsExactly(USER_START_MSG); + + verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY); } private void startUserAssertions( @@ -303,6 +308,7 @@ public class UserControllerTest { assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state); assertEquals("Unexpected old user id", 0, reportMsg.arg1); assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2); + verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY); } @Test @@ -313,6 +319,8 @@ public class UserControllerTest { mUserController.startUserInForeground(NONEXIST_USER_ID); verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean()); verify(mInjector.getWindowManager()).setSwitchingUser(false); + + verifyUserNeverAssignedToDisplay(); } @Test @@ -395,6 +403,7 @@ public class UserControllerTest { verify(mInjector, times(0)).dismissKeyguard(any(), anyString()); verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); continueUserSwitchAssertions(TEST_USER_ID, false); + verifyOnUserStarting(USER_SYSTEM, /* visible= */ false); } @Test @@ -403,7 +412,7 @@ public class UserControllerTest { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); + mUserController.startUser(TEST_USER_ID, /* foreground=*/ true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -415,6 +424,7 @@ public class UserControllerTest { verify(mInjector, times(1)).dismissKeyguard(any(), anyString()); verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen(); continueUserSwitchAssertions(TEST_USER_ID, false); + verifyOnUserStarting(USER_SYSTEM, /* visible= */ false); } @Test @@ -423,7 +433,7 @@ public class UserControllerTest { /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); // Start user -- this will update state of mUserController - mUserController.startUser(TEST_USER_ID, true); + mUserController.startUser(TEST_USER_ID, /* foreground=*/ true); Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG); assertNotNull(reportMsg); UserState userState = (UserState) reportMsg.obj; @@ -521,6 +531,7 @@ public class UserControllerTest { assertFalse(mUserController.canStartMoreUsers()); assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}), mUserController.getRunningUsersLU()); + verifyOnUserStarting(USER_SYSTEM, /* visible= */ false); } /** @@ -530,7 +541,7 @@ public class UserControllerTest { */ @Test public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode() - throws InterruptedException, RemoteException { + throws Exception { mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true); @@ -645,6 +656,8 @@ public class UserControllerTest { setUpUser(TEST_USER_ID1, 0); assertThrows(IllegalArgumentException.class, () -> mUserController.startProfile(TEST_USER_ID1)); + + verifyUserNeverAssignedToDisplay(); } @Test @@ -660,6 +673,8 @@ public class UserControllerTest { setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */ false, UserManager.USER_TYPE_PROFILE_MANAGED); assertThat(mUserController.startProfile(TEST_USER_ID1)).isFalse(); + + verifyUserNeverAssignedToDisplay(); } @Test @@ -949,6 +964,10 @@ public class UserControllerTest { verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId); } + private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) { + verify(mInjector).onUserStarting(userId, visible); + } + // Should be public to allow mocking private static class TestInjector extends UserController.Injector { public final TestHandler mHandler; @@ -1084,6 +1103,11 @@ public class UserControllerTest { protected LockPatternUtils getLockPatternUtils() { return mLockPatternUtilsMock; } + + @Override + void onUserStarting(@UserIdInt int userId, boolean visible) { + Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")"); + } } private static class TestHandler extends Handler {