Creates a mechanism to override UserManager.isHeadlessSystemUserMode().
By default, that mode is set by the OEM and is immutable, but this change allows developers to change it on non-user builds by calling `cmd user set-system-user-mode-emulation`. Notice that changing the headless system user mode is not trivial; besides the API implementation itself, it's also necessary to: 1. Check if it's possible (for example, user 0 cannot be locked) 2. Change the system user type in the persistent XML 3. Create a full user (when going full -> headless) 4. Re-install apps for system user (when going headless -> full) (NOTE: steps 1 and 4 will be done in a follow-up CL - commit 3a029b05d39018ba26f8b218bab968898362138e) Bug: 226643927 Test: adb shell cmd user set-system-user-mode-emulation full # on automotive Test: adb shell cmd user set-system-user-mode-emulation headless # on phone Test: adb shell dumpsys user | grep -A 1 headless Change-Id: I5ee7d41b5baf5c8a826a1c0b9e599955eb9ad23c
This commit is contained in:
@@ -90,6 +90,7 @@ import java.util.Set;
|
||||
public class UserManager {
|
||||
|
||||
private static final String TAG = "UserManager";
|
||||
|
||||
@UnsupportedAppUsage
|
||||
private final IUserManager mService;
|
||||
/** Holding the Application context (not constructor param context). */
|
||||
@@ -1565,6 +1566,27 @@ public class UserManager {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface UserRestrictionKey {}
|
||||
|
||||
/**
|
||||
* Property used to override whether the device uses headless system user mode.
|
||||
*
|
||||
* <p>Only used on non-user builds.
|
||||
*
|
||||
* <p><b>NOTE: </b>setting this variable directly won't properly change the headless system user
|
||||
* mode behavior and might put the device in a bad state; the system user mode should be changed
|
||||
* using {@code cmd user set-system-user-mode-emulation} instead.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY =
|
||||
"persist.debug.user_mode_emulation";
|
||||
|
||||
/** @hide */
|
||||
public static final String SYSTEM_USER_MODE_EMULATION_DEFAULT = "default";
|
||||
/** @hide */
|
||||
public static final String SYSTEM_USER_MODE_EMULATION_FULL = "full";
|
||||
/** @hide */
|
||||
public static final String SYSTEM_USER_MODE_EMULATION_HEADLESS = "headless";
|
||||
|
||||
private static final String ACTION_CREATE_USER = "android.os.action.CREATE_USER";
|
||||
|
||||
/**
|
||||
@@ -2022,7 +2044,28 @@ public class UserManager {
|
||||
* @return whether the device is running in a headless system user mode.
|
||||
*/
|
||||
public static boolean isHeadlessSystemUserMode() {
|
||||
return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
|
||||
final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
|
||||
if (!Build.isDebuggable()) {
|
||||
return realMode;
|
||||
}
|
||||
|
||||
final String emulatedMode = SystemProperties.get(DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY);
|
||||
switch (emulatedMode) {
|
||||
case SYSTEM_USER_MODE_EMULATION_FULL:
|
||||
Log.d(TAG, "isHeadlessSystemUserMode(): emulating as false");
|
||||
return false;
|
||||
case SYSTEM_USER_MODE_EMULATION_HEADLESS:
|
||||
Log.d(TAG, "isHeadlessSystemUserMode(): emulating as true");
|
||||
return true;
|
||||
case SYSTEM_USER_MODE_EMULATION_DEFAULT:
|
||||
case "": // property not set
|
||||
return realMode;
|
||||
default:
|
||||
Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
|
||||
+ DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY + " (" + emulatedMode + "); using"
|
||||
+ " default value (headless=" + realMode + ")");
|
||||
return realMode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -111,6 +111,7 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.IAppOpsService;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.internal.os.RoSystemProperties;
|
||||
import com.android.internal.util.DumpUtils;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.Preconditions;
|
||||
@@ -194,6 +195,7 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
private static final String ATTR_RESTRICTED_PROFILE_PARENT_ID = "restrictedProfileParentId";
|
||||
private static final String ATTR_SEED_ACCOUNT_NAME = "seedAccountName";
|
||||
private static final String ATTR_SEED_ACCOUNT_TYPE = "seedAccountType";
|
||||
|
||||
private static final String TAG_GUEST_RESTRICTIONS = "guestRestrictions";
|
||||
private static final String TAG_USERS = "users";
|
||||
private static final String TAG_USER = "user";
|
||||
@@ -213,6 +215,7 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
"lastRequestQuietModeEnabledCall";
|
||||
private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS =
|
||||
"ignorePrepareStorageErrors";
|
||||
|
||||
private static final String ATTR_KEY = "key";
|
||||
private static final String ATTR_VALUE_TYPE = "type";
|
||||
private static final String ATTR_MULTIPLE = "m";
|
||||
@@ -721,6 +724,7 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
mLockPatternUtils = new LockPatternUtils(mContext);
|
||||
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
|
||||
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
|
||||
emulateSystemUserModeIfNeeded();
|
||||
}
|
||||
|
||||
void systemReady() {
|
||||
@@ -2927,6 +2931,87 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the device is really headless system user mode, ignoring system user mode
|
||||
* emulation.
|
||||
*/
|
||||
private boolean isReallyHeadlessSystemUserMode() {
|
||||
return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on boot to change the system user mode (for example, from headless to full or
|
||||
* vice versa) for development purposes.
|
||||
*/
|
||||
private void emulateSystemUserModeIfNeeded() {
|
||||
if (!Build.isDebuggable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String emulatedValue = SystemProperties
|
||||
.get(UserManager.DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY);
|
||||
if (TextUtils.isEmpty(emulatedValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean newHeadlessSystemUserMode;
|
||||
switch (emulatedValue) {
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
|
||||
newHeadlessSystemUserMode = false;
|
||||
break;
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
|
||||
newHeadlessSystemUserMode = true;
|
||||
break;
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
|
||||
newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
|
||||
break;
|
||||
default:
|
||||
Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
|
||||
+ "property %s: %s", UserManager.DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY,
|
||||
emulatedValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update system user type
|
||||
synchronized (mPackagesLock) {
|
||||
synchronized (mUsersLock) {
|
||||
final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
|
||||
if (systemUserData == null) {
|
||||
Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data");
|
||||
return;
|
||||
}
|
||||
final int oldFlags = systemUserData.info.flags;
|
||||
final int newFlags;
|
||||
final String newUserType;
|
||||
if (newHeadlessSystemUserMode) {
|
||||
newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
|
||||
newFlags = oldFlags & ~UserInfo.FLAG_FULL;
|
||||
} else {
|
||||
newUserType = UserManager.USER_TYPE_FULL_SYSTEM;
|
||||
newFlags = oldFlags | UserInfo.FLAG_FULL;
|
||||
}
|
||||
|
||||
if (systemUserData.info.userType.equals(newUserType)) {
|
||||
Slogf.d(LOG_TAG, "emulateSystemUserModeIfNeeded(): system user type is already "
|
||||
+ "%s, returning", newUserType);
|
||||
return;
|
||||
}
|
||||
Slogf.i(LOG_TAG, "Persisting emulated system user data: type changed from %s to "
|
||||
+ "%s, flags changed from %s to %s",
|
||||
systemUserData.info.userType, newUserType,
|
||||
UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags));
|
||||
systemUserData.info.userType = newUserType;
|
||||
systemUserData.info.flags = newFlags;
|
||||
writeUserLP(systemUserData);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/203885212): need to update the system user packages; the "easiest way" woulbe
|
||||
// be setting "persist.pm.mock-upgrade", but that would require granting a new selinux
|
||||
// permission. Another options would be providing an internal API, or setting a
|
||||
// debug.xxx property - either way, we'll do that in a follow-up CL
|
||||
}
|
||||
|
||||
@GuardedBy({"mRestrictionsLock", "mPackagesLock"})
|
||||
private void readUserListLP() {
|
||||
if (!mUserListFile.exists()) {
|
||||
@@ -3293,8 +3378,9 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
|
||||
| UserInfo.FLAG_PRIMARY;
|
||||
// Create the system user
|
||||
String systemUserType = UserManager.isHeadlessSystemUserMode() ?
|
||||
UserManager.USER_TYPE_SYSTEM_HEADLESS : UserManager.USER_TYPE_FULL_SYSTEM;
|
||||
String systemUserType = UserManager.isHeadlessSystemUserMode()
|
||||
? UserManager.USER_TYPE_SYSTEM_HEADLESS
|
||||
: UserManager.USER_TYPE_FULL_SYSTEM;
|
||||
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
|
||||
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType);
|
||||
UserData userData = putUserInfo(system);
|
||||
@@ -5514,6 +5600,13 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
pw.println(" --mode MODE: shows what errors would be if device used mode MODE");
|
||||
pw.println(" (where MODE is the allowlist mode integer as defined by "
|
||||
+ "config_userTypePackageWhitelistMode)");
|
||||
pw.println();
|
||||
pw.println(" set-system-user-mode-emulation [headless | full | default]");
|
||||
pw.println(" Changes whether the system user is headless or full.");
|
||||
pw.println(" WARNING: this command is meant just for development and debugging "
|
||||
+ "purposes.");
|
||||
pw.println(" It should NEVER be used on automated tests.");
|
||||
pw.println();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -5528,6 +5621,8 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
return runList();
|
||||
case "report-system-user-package-whitelist-problems":
|
||||
return runReportPackageAllowlistProblems();
|
||||
case "set-system-user-mode-emulation":
|
||||
return runSetSystemUserModeEmulation();
|
||||
default:
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
@@ -5657,6 +5752,82 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private int runSetSystemUserModeEmulation() {
|
||||
if (!confirmBuildIsDebuggable() || !confirmIsCalledByRoot()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
final String mode = getNextArgRequired();
|
||||
final boolean isHeadlessSystemUserModeCurrently = UserManager
|
||||
.isHeadlessSystemUserMode();
|
||||
final boolean changed;
|
||||
|
||||
switch (mode) {
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
|
||||
changed = isHeadlessSystemUserModeCurrently;
|
||||
break;
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
|
||||
changed = !isHeadlessSystemUserModeCurrently;
|
||||
break;
|
||||
case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
|
||||
changed = true; // Always update when resetting to default
|
||||
break;
|
||||
default:
|
||||
pw.printf("Invalid arg: %s\n", mode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
pw.printf("No change needed, system user is already %s\n",
|
||||
isHeadlessSystemUserModeCurrently ? "headless" : "full");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Slogf.d(LOG_TAG, "Updating system property %s to %s",
|
||||
UserManager.DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY, mode);
|
||||
|
||||
// TODO(b/203885212): temp try/catch until the selinux permission is granted
|
||||
try {
|
||||
SystemProperties.set(UserManager.DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY, mode);
|
||||
pw.println("System user mode changed - please reboot (or restart Android runtime) "
|
||||
+ "to continue");
|
||||
pw.println("NOTICE: after restart, some apps might be uninstalled (and their data "
|
||||
+ "will be lost)");
|
||||
} catch (RuntimeException e) {
|
||||
pw.printf("Failed to set property %s (%s). You might need to run "
|
||||
+ "'adb shell setenforce 0' and try again.\n",
|
||||
UserManager.DEV_HEADLESS_SYSTEM_USER_MODE_PROPERTY, e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the build is debuggable
|
||||
*
|
||||
* <p>It logs an error when it isn't.
|
||||
*/
|
||||
private boolean confirmBuildIsDebuggable() {
|
||||
if (Build.isDebuggable()) {
|
||||
return true;
|
||||
}
|
||||
getErrPrintWriter().println("Command not available on user builds");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms if the command is called when {@code adb} is rooted.
|
||||
*
|
||||
* <p>It logs an error when it isn't.
|
||||
*/
|
||||
private boolean confirmIsCalledByRoot() {
|
||||
if (Binder.getCallingUid() == Process.ROOT_UID) {
|
||||
return true;
|
||||
}
|
||||
getErrPrintWriter().println("Command only available on root user");
|
||||
return false;
|
||||
}
|
||||
} // class Shell
|
||||
|
||||
@Override
|
||||
@@ -5741,7 +5912,11 @@ public class UserManagerService extends IUserManager.Stub {
|
||||
com.android.internal.R.bool.config_guestUserEphemeral));
|
||||
pw.println(" Force ephemeral users: " + mForceEphemeralUsers);
|
||||
pw.println(" Is split-system user: " + UserManager.isSplitSystemUser());
|
||||
pw.println(" Is headless-system mode: " + UserManager.isHeadlessSystemUserMode());
|
||||
final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
|
||||
pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode);
|
||||
if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
|
||||
pw.println(" (emulated by 'cmd user set-system-user-mode-emulation')");
|
||||
}
|
||||
pw.println(" User version: " + mUserVersion);
|
||||
pw.println(" Owner name: " + getOwnerName());
|
||||
if (DBG_ALLOCATION) {
|
||||
|
||||
Reference in New Issue
Block a user