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:
Felipe Leme
2022-04-06 17:02:55 -07:00
parent 12b74d24d2
commit 3b9744ea78
2 changed files with 222 additions and 4 deletions

View File

@@ -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;
}
}
/**

View File

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