diff options
| author | 2022-11-28 18:09:12 +0000 | |
|---|---|---|
| committer | 2023-01-13 20:36:42 +0000 | |
| commit | 81299d541c31a2be674c4bc111d379e6f93314ce (patch) | |
| tree | b2ebda8bcdfa51f5ed8852f6c02891b27d303eb4 | |
| parent | 7cc40702af9f5a0ea3b858d12102f887b59ad977 (diff) | |
Headless: Switch to supplied user or previous user after boot.
This adds a system API to specify which user should be switched to
after boot on headless, for use by Automotive devices (for example).
On headless, after boot, the foreground user will be the specified user,
if there is one, or the previous foreground user otherwise.
Bug: 242195409
Test: atest UserManagerServiceTest
Change-Id: I16971511a1303ca718051917c6618655ef0c87b1
| -rw-r--r-- | core/api/system-current.txt | 1 | ||||
| -rw-r--r-- | core/api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/os/IUserManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/os/UserManager.java | 34 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerInternal.java | 16 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/UserManagerService.java | 99 | ||||
| -rw-r--r-- | services/java/com/android/server/BootUserInitializer.java | 63 | ||||
| -rw-r--r-- | services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java | 121 |
8 files changed, 284 insertions, 55 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ceed59f79464..a70dcc775dbc 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10411,6 +10411,7 @@ package android.os { method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible(); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserName(@Nullable String); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean someUserHasAccount(@NonNull String, @NonNull String); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 3bc11facad4a..1a8ea6f1087a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2025,6 +2025,7 @@ package android.os { method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createProfileForUser(@Nullable String, @NonNull String, int, int, @Nullable String[]); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle getBootUser(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 3b4e8cd39697..d1d331550ab9 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -142,4 +142,8 @@ interface IUserManager { long getUserStartRealtime(); long getUserUnlockRealtime(); boolean setUserEphemeral(int userId, boolean enableEphemeral); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})") + void setBootUser(int userId); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS})") + int getBootUser(); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 0e4c2b2928d6..f6859ba57ff4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -5656,6 +5656,40 @@ public class UserManager { } } + /** + * Sets the user who should be in the foreground when boot completes. This should be called + * during boot, and the provided user must be a full user (i.e. not a profile). + * + * @hide + */ + @SystemApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + public void setBootUser(@NonNull UserHandle bootUser) { + try { + mService.setBootUser(bootUser.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns the user who should be in the foreground when boot completes. + * + * @hide + */ + @TestApi + @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}) + @SuppressWarnings("[AndroidFrameworkContextUserId]") + public @NonNull UserHandle getBootUser() { + try { + return UserHandle.of(mService.getBootUser()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */ private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props"; diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 2ae8b52da172..4acd81560d4a 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -485,4 +485,20 @@ public abstract class UserManagerInternal { * @see UserManager#isMainUser() */ public abstract @UserIdInt int getMainUserId(); + + /** + * Returns the id of the user which should be in the foreground after boot completes. + * + * <p>If a boot user has been provided by calling {@link UserManager#setBootUser}, the + * returned value will be whatever was specified, as long as that user exists and can be + * switched to. + * + * <p>Otherwise, in {@link UserManager#isHeadlessSystemUserMode() headless system user mode}, + * this will be the user who was last in the foreground on this device. If there is no + * switchable user on the device, a new user will be created and its id will be returned. + * + * <p>In non-headless system user mode, the return value will be {@link UserHandle#USER_SYSTEM}. + */ + public abstract @UserIdInt int getBootUser() + throws UserManager.CheckedUserOperationException; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 81f83b0591c4..dc8786280633 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -21,6 +21,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY; import static android.os.UserManager.DISALLOW_USER_SWITCH; import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; +import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; import android.Manifest; import android.accounts.Account; @@ -637,6 +638,9 @@ public class UserManagerService extends IUserManager.Stub { private final UserVisibilityMediator mUserVisibilityMediator; + @GuardedBy("mUsersLock") + private @UserIdInt int mBootUser = UserHandle.USER_NULL; + private static UserManagerService sInstance; public static UserManagerService getInstance() { @@ -936,6 +940,26 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public void setBootUser(@UserIdInt int userId) { + checkCreateUsersPermission("Set boot user"); + synchronized (mUsersLock) { + // TODO(b/263381643): Change to EventLog. + Slogf.i(LOG_TAG, "setBootUser %d", userId); + mBootUser = userId; + } + } + + @Override + public @UserIdInt int getBootUser() { + checkCreateUsersPermission("Get boot user"); + try { + return mLocalService.getBootUser(); + } catch (UserManager.CheckedUserOperationException e) { + throw e.toServiceSpecificException(); + } + } + + @Override public int getPreviousFullUserToEnterForeground() { checkQueryOrCreateUsersPermission("get previous user"); int previousUser = UserHandle.USER_NULL; @@ -1574,6 +1598,8 @@ public class UserManagerService extends IUserManager.Stub { Slog.w(LOG_TAG, "System user instantiated at least " + number + " times"); } name = getOwnerName(); + } else if (orig.isMain()) { + name = getOwnerName(); } else if (orig.isGuest()) { name = getGuestName(); } @@ -4557,7 +4583,7 @@ public class UserManagerService extends IUserManager.Stub { UserHandle.USER_NULL, null); if (userInfo == null) { - throw new ServiceSpecificException(UserManager.USER_OPERATION_ERROR_UNKNOWN); + throw new ServiceSpecificException(USER_OPERATION_ERROR_UNKNOWN); } } catch (UserManager.CheckedUserOperationException e) { throw e.toServiceSpecificException(); @@ -4686,7 +4712,7 @@ public class UserManagerService extends IUserManager.Stub { if (parent == null) { throwCheckedUserOperationException( "Cannot find user data for parent user " + parentId, - UserManager.USER_OPERATION_ERROR_UNKNOWN); + USER_OPERATION_ERROR_UNKNOWN); } } if (!preCreate && !canAddMoreUsersOfType(userTypeDetails)) { @@ -4714,7 +4740,7 @@ public class UserManagerService extends IUserManager.Stub { && !isCreationOverrideEnabled()) { throwCheckedUserOperationException( "Cannot add restricted profile - parent user must be system", - UserManager.USER_OPERATION_ERROR_UNKNOWN); + USER_OPERATION_ERROR_UNKNOWN); } userId = getNextAvailableId(); @@ -6480,6 +6506,9 @@ public class UserManagerService extends IUserManager.Stub { if (DBG_ALLOCATION) { pw.println(" System user allocations: " + mUser0Allocations.get()); } + synchronized (mUsersLock) { + pw.println(" Boot user: " + mBootUser); + } pw.println(); pw.println("Number of listeners for"); @@ -6672,6 +6701,18 @@ public class UserManagerService extends IUserManager.Stub { return mLocalService.isUserInitialized(userId); } + /** + * Creates a new user, intended to be the initial user on a device in headless system user mode. + */ + private UserInfo createInitialUserForHsum() throws UserManager.CheckedUserOperationException { + final int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN; + + // Null name will be replaced with "Owner" on-demand to allow for localisation. + return createUserInternalUnchecked(/* name= */ null, UserManager.USER_TYPE_FULL_SECONDARY, + flags, UserHandle.USER_NULL, /* preCreate= */ false, + /* disallowedPackages= */ null, /* token= */ null); + } + private class LocalService extends UserManagerInternal { @Override public void setDevicePolicyUserRestrictions(@UserIdInt int originatingUserId, @@ -7105,6 +7146,56 @@ public class UserManagerService extends IUserManager.Stub { return getMainUserIdUnchecked(); } + @Override + public @UserIdInt int getBootUser() throws UserManager.CheckedUserOperationException { + synchronized (mUsersLock) { + // TODO(b/242195409): On Automotive, block if boot user not provided. + if (mBootUser != UserHandle.USER_NULL) { + final UserData userData = mUsers.get(mBootUser); + if (userData != null && userData.info.supportsSwitchToByUser()) { + Slogf.i(LOG_TAG, "Using provided boot user: %d", mBootUser); + return mBootUser; + } else { + Slogf.w(LOG_TAG, + "Provided boot user cannot be switched to: %d", mBootUser); + } + } + } + + if (isHeadlessSystemUserMode()) { + // Return the previous foreground user, if there is one. + final int previousUser = getPreviousFullUserToEnterForeground(); + if (previousUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); + return previousUser; + } + // No previous user. Return the first switchable user if there is one. + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData userData = mUsers.valueAt(i); + if (userData.info.supportsSwitchToByUser()) { + int firstSwitchable = userData.info.id; + Slogf.i(LOG_TAG, + "Boot user is first switchable user %d", firstSwitchable); + return firstSwitchable; + } + } + } + // No switchable users. Create the initial user. + final UserInfo newInitialUser = createInitialUserForHsum(); + if (newInitialUser == null) { + throw new UserManager.CheckedUserOperationException( + "Initial user creation failed", USER_OPERATION_ERROR_UNKNOWN); + } + Slogf.i(LOG_TAG, + "No switchable users. Boot user is new user %d", newInitialUser.id); + return newInitialUser.id; + } + // Not HSUM, return system user. + return UserHandle.USER_SYSTEM; + } + } // class LocalService @@ -7124,7 +7215,7 @@ public class UserManagerService extends IUserManager.Stub { + restriction + " is enabled."; Slog.w(LOG_TAG, errorMessage); throw new UserManager.CheckedUserOperationException(errorMessage, - UserManager.USER_OPERATION_ERROR_UNKNOWN); + USER_OPERATION_ERROR_UNKNOWN); } } diff --git a/services/java/com/android/server/BootUserInitializer.java b/services/java/com/android/server/BootUserInitializer.java index deebfc7f7d07..3d71739924f7 100644 --- a/services/java/com/android/server/BootUserInitializer.java +++ b/services/java/com/android/server/BootUserInitializer.java @@ -17,7 +17,6 @@ package com.android.server; import android.annotation.UserIdInt; import android.content.ContentResolver; -import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -27,8 +26,6 @@ import com.android.server.pm.UserManagerInternal; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; -import java.util.List; - /** * Class responsible for booting the device in the proper user on headless system user mode. * @@ -56,50 +53,18 @@ final class BootUserInitializer { // this class or the setup wizard app provisionHeadlessSystemUser(); - UserManagerInternal um = LocalServices.getService(UserManagerInternal.class); - t.traceBegin("get-existing-users"); - List<UserInfo> existingUsers = um.getUsers(/* excludeDying= */ true); - t.traceEnd(); - - Slogf.d(TAG, "%d existing users", existingUsers.size()); - - int initialUserId = UserHandle.USER_NULL; - - for (int i = 0; i < existingUsers.size(); i++) { - UserInfo user = existingUsers.get(i); - if (DEBUG) { - Slogf.d(TAG, "User at position %d: %s", i, user.toFullString()); - } - if (user.id != UserHandle.USER_SYSTEM && user.isFull()) { - if (DEBUG) { - Slogf.d(TAG, "Found initial user: %d", user.id); - } - initialUserId = user.id; - break; - } - } + unlockSystemUser(t); - if (initialUserId == UserHandle.USER_NULL) { - Slogf.d(TAG, "Creating initial user"); - t.traceBegin("create-initial-user"); - try { - int flags = UserInfo.FLAG_ADMIN | UserInfo.FLAG_MAIN; - // TODO(b/204091126): proper name for user - UserInfo newUser = um.createUserEvenWhenDisallowed("Real User", - UserManager.USER_TYPE_FULL_SECONDARY, flags, - /* disallowedPackages= */ null, /* token= */ null); - Slogf.i(TAG, "Created initial user: %s", newUser.toFullString()); - initialUserId = newUser.id; - } catch (Exception e) { - Slogf.wtf(TAG, "failed to created initial user", e); - return; - } finally { - t.traceEnd(); // create-initial-user - } + try { + t.traceBegin("getBootUser"); + int bootUser = LocalServices.getService(UserManagerInternal.class).getBootUser(); + t.traceEnd(); + t.traceBegin("switchToBootUser-" + bootUser); + switchToBootUser(bootUser); + t.traceEnd(); + } catch (UserManager.CheckedUserOperationException e) { + Slogf.wtf(TAG, "Failed to created boot user", e); } - - unlockSystemUser(t); - switchToInitialUser(initialUserId); } /* TODO(b/261791491): STOPSHIP - SUW should be responsible for this. */ @@ -152,12 +117,12 @@ final class BootUserInitializer { } } - private void switchToInitialUser(@UserIdInt int initialUserId) { - Slogf.i(TAG, "Switching to initial user %d", initialUserId); - boolean started = mAms.startUserInForegroundWithListener(initialUserId, + private void switchToBootUser(@UserIdInt int bootUserId) { + Slogf.i(TAG, "Switching to boot user %d", bootUserId); + boolean started = mAms.startUserInForegroundWithListener(bootUserId, /* unlockListener= */ null); if (!started) { - Slogf.wtf(TAG, "Failed to start user %d in foreground", initialUserId); + Slogf.wtf(TAG, "Failed to start user %d in foreground", bootUserId); } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 1367af8eede3..f13de1243955 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -20,25 +20,31 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; import android.app.ActivityManagerInternal; import android.content.Context; +import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; +import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import androidx.test.annotation.UiThreadTest; import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import com.android.internal.widget.LockSettingsInternal; import com.android.server.ExtendedMockitoTestCase; import com.android.server.LocalServices; import com.android.server.am.UserState; import com.android.server.pm.UserManagerService.UserData; +import com.android.server.storage.DeviceStorageMonitorInternal; import org.junit.After; import org.junit.Before; @@ -86,6 +92,10 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { private @Mock PackageManagerService mMockPms; private @Mock UserDataPreparer mMockUserDataPreparer; private @Mock ActivityManagerInternal mActivityManagerInternal; + private @Mock DeviceStorageMonitorInternal mDeviceStorageMonitorInternal; + private @Mock StorageManager mStorageManager; + private @Mock LockSettingsInternal mLockSettingsInternal; + private @Mock PackageManagerInternal mPackageManagerInternal; /** * Reference to the {@link UserManagerService} being tested. @@ -101,7 +111,8 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { protected void initializeSession(StaticMockitoSessionBuilder builder) { builder .spyStatic(UserManager.class) - .spyStatic(LocalServices.class); + .spyStatic(LocalServices.class) + .mockStatic(Settings.Global.class); } @Before @@ -112,6 +123,14 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { // Called when WatchedUserStates is constructed doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache()); + // Called when creating new users + when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false); + mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal); + when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager); + mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal); + mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal); + doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); + // Must construct UserManagerService in the UiThread mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer, mPackagesLock, mRealContext.getDataDir(), mUsers); @@ -223,6 +242,87 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse(); } + @Test + public void testSetBootUser_SuppliedUserIsSwitchable() throws Exception { + addUser(USER_ID); + addUser(OTHER_USER_ID); + + mUms.setBootUser(OTHER_USER_ID); + + assertWithMessage("getBootUser") + .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID); + } + + @Test + public void testSetBootUser_NotHeadless_SuppliedUserIsNotSwitchable() throws Exception { + setSystemUserHeadless(false); + addUser(USER_ID); + addUser(OTHER_USER_ID); + addDefaultProfileAndParent(); + + mUms.setBootUser(PROFILE_USER_ID); + + assertWithMessage("getBootUser") + .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM); + } + + @Test + public void testSetBootUser_Headless_SuppliedUserIsNotSwitchable() throws Exception { + setSystemUserHeadless(true); + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + addDefaultProfileAndParent(); + + mUms.setBootUser(PROFILE_USER_ID); + + // Boot user not switchable so return most recently in foreground. + assertWithMessage("getBootUser") + .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID); + } + + @Test + public void testGetBootUser_NotHeadless_ReturnsSystemUser() throws Exception { + setSystemUserHeadless(false); + addUser(USER_ID); + addUser(OTHER_USER_ID); + + assertWithMessage("getBootUser") + .that(mUmi.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM); + } + + @Test + public void testGetBootUser_Headless_ReturnsMostRecentlyInForeground() throws Exception { + setSystemUserHeadless(true); + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + assertWithMessage("getBootUser") + .that(mUmi.getBootUser()).isEqualTo(OTHER_USER_ID); + } + + @Test + public void testGetBootUser_Headless_UserCreatedIfOnlySystemUserExists() throws Exception { + setSystemUserHeadless(true); + + int bootUser = mUmi.getBootUser(); + + assertWithMessage("getStartingUser") + .that(bootUser).isNotEqualTo(UserHandle.USER_SYSTEM); + + UserData newUser = mUsers.get(bootUser); + assertWithMessage("New boot user is a full user") + .that(newUser.info.isFull()).isTrue(); + assertWithMessage("New boot user is an admin user") + .that(newUser.info.isAdmin()).isTrue(); + assertWithMessage("New boot user is the main user") + .that(newUser.info.isMain()).isTrue(); + } + private void mockCurrentUser(@UserIdInt int userId) { mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal); @@ -248,7 +348,7 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { private void addUser(@UserIdInt int userId) { TestUserData userData = new TestUserData(userId); - + userData.info.flags = UserInfo.FLAG_FULL; addUserData(userData); } @@ -277,6 +377,23 @@ public final class UserManagerServiceTest extends ExtendedMockitoTestCase { mUsers.put(userData.info.id, userData); } + private void setSystemUserHeadless(boolean headless) { + UserData systemUser = mUsers.get(UserHandle.USER_SYSTEM); + if (headless) { + systemUser.info.flags &= ~UserInfo.FLAG_FULL; + systemUser.info.userType = UserManager.USER_TYPE_SYSTEM_HEADLESS; + } else { + systemUser.info.flags |= UserInfo.FLAG_FULL; + systemUser.info.userType = UserManager.USER_TYPE_FULL_SYSTEM; + } + doReturn(headless).when(() -> UserManager.isHeadlessSystemUserMode()); + } + + private void setLastForegroundTime(@UserIdInt int userId, long timeMillis) { + UserData userData = mUsers.get(userId); + userData.mLastEnteredForegroundTimeMillis = timeMillis; + } + private static final class TestUserData extends UserData { @SuppressWarnings("deprecation") |