Merge "Headless: Switch to supplied user or previous user after boot."

This commit is contained in:
Oli Lan
2023-01-16 10:21:44 +00:00
committed by Android (Google) Code Review
8 changed files with 285 additions and 56 deletions

View File

@@ -10403,6 +10403,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);

View File

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

View File

@@ -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();
}

View File

@@ -5671,6 +5671,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";

View File

@@ -513,4 +513,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;
}

View File

@@ -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() {
@@ -935,6 +939,26 @@ public class UserManagerService extends IUserManager.Stub {
return UserHandle.USER_NULL;
}
@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");
@@ -1569,6 +1593,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();
}
@@ -4535,7 +4561,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();
@@ -4664,7 +4690,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)) {
@@ -4692,7 +4718,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();
@@ -6459,6 +6485,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");
@@ -6651,6 +6680,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,
@@ -7094,6 +7135,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
@@ -7113,7 +7204,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);
}
}

View File

@@ -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;
}
}
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
}
}
unlockSystemUser(t);
switchToInitialUser(initialUserId);
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);
}
}
/* 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);
}
}
}

View File

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