Merge "Added user visibility callbacks on SystemService."

This commit is contained in:
Felipe Leme
2022-10-26 15:43:40 +00:00
committed by Android (Google) Code Review
7 changed files with 216 additions and 43 deletions

View File

@@ -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

View File

@@ -472,6 +472,18 @@ public abstract class SystemService {
public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
}
/**
* The {@link UserManager#isUserVisible() user visibility} changed.
*
* <p>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

View File

@@ -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.
*
* <p><b>NOTE: </b>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?");
}

View File

@@ -8346,14 +8346,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) {

View File

@@ -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)

View File

@@ -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()}).
*
* <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
* the user starting or stopping.
*
* <p>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;
}
@@ -3539,5 +3634,10 @@ class UserController implements Handler.Callback {
boolean isUsersOnSecondaryDisplaysEnabled() {
return UserManager.isUsersOnSecondaryDisplaysEnabled();
}
void onUserStarting(int userId, boolean visible) {
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
visible);
}
}
}

View File

@@ -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 {