blob: 835ab6a8e8bce6212330e4bf53c32a32e08822a1 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.R;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.Slogf;
import java.io.PrintWriter;
import java.util.List;
/**
* Shell command implementation for the user manager service
*/
public class UserManagerServiceShellCommand extends ShellCommand {
private static final String LOG_TAG = "UserManagerServiceShellCommand";
@NonNull
private final UserManagerService mService;
@NonNull
private final UserSystemPackageInstaller mSystemPackageInstaller;
@NonNull
private final LockPatternUtils mLockPatternUtils;
@NonNull
private final Context mContext;
UserManagerServiceShellCommand(UserManagerService service,
UserSystemPackageInstaller userSystemPackageInstaller,
LockPatternUtils lockPatternUtils,
Context context) {
mService = service;
mSystemPackageInstaller = userSystemPackageInstaller;
mLockPatternUtils = lockPatternUtils;
mContext = context;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("User manager (user) commands:");
pw.println(" help");
pw.println(" Prints this help text.");
pw.println();
pw.println(" list [-v | --verbose] [--all]");
pw.println(" Prints all users on the system.");
pw.println();
pw.println(" report-system-user-package-whitelist-problems [-v | --verbose] "
+ "[--critical-only] [--mode MODE]");
pw.println(" Reports all issues on user-type package allowlist XML files. Options:");
pw.println(" -v | --verbose: shows extra info, like number of issues");
pw.println(" --critical-only: show only critical issues, excluding warnings");
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 [--reboot | --no-restart] "
+ "<headless | full | default>");
pw.println(" Changes whether the system user is headless, full, or default (as "
+ "defined by OEM).");
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(" NOTE: by default it restarts the Android runtime, unless called with");
pw.println(" --reboot (which does a full reboot) or");
pw.println(" --no-restart (which requires a manual restart)");
pw.println();
pw.println(" is-headless-system-user-mode [-v | --verbose]");
pw.println(" Checks whether the device uses headless system user mode.");
pw.println(" is-visible-background-users-on-default-display-supported [-v | --verbose]");
pw.println(" Checks whether the device allows users to be start visible on background "
+ "in the default display.");
pw.println(" It returns the effective mode, even when using emulation");
pw.println(" (to get the real mode as well, use -v or --verbose)");
pw.println();
pw.println(" is-visible-background-users-supported [-v | --verbose]");
pw.println(" Checks whether the device allows users to be start visible on background.");
pw.println(" It returns the effective mode, even when using emulation");
pw.println(" (to get the real mode as well, use -v or --verbose)");
pw.println();
pw.println(" is-user-visible [--display DISPLAY_ID] <USER_ID>");
pw.println(" Checks if the given user is visible in the given display.");
pw.println(" If the display option is not set, it uses the user's context to check");
pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())");
pw.println();
pw.println(" get-main-user ");
pw.println(" Displays main user id or message if there is no main user");
pw.println();
}
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(null);
}
try {
switch(cmd) {
case "list":
return runList();
case "report-system-user-package-whitelist-problems":
return runReportPackageAllowlistProblems();
case "set-system-user-mode-emulation":
return runSetSystemUserModeEmulation();
case "is-headless-system-user-mode":
return runIsHeadlessSystemUserMode();
case "is-visible-background-users-supported":
return runIsVisibleBackgroundUserSupported();
case "is-visible-background-users-on-default-display-supported":
return runIsVisibleBackgroundUserOnDefaultDisplaySupported();
case "is-user-visible":
return runIsUserVisible();
case "get-main-user":
return runGetMainUserId();
case "can-switch-to-headless-system-user":
return canSwitchToHeadlessSystemUser();
case "is-main-user-permanent-admin":
return isMainUserPermanentAdmin();
default:
return handleDefaultCommands(cmd);
}
} catch (RemoteException e) {
getOutPrintWriter().println("Remote exception: " + e);
}
return -1;
}
private int runList() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
boolean all = false;
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
case "--all":
all = true;
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
final IActivityManager am = ActivityManager.getService();
final List<UserInfo> users = mService.getUsers(/* excludePartial= */ !all,
/* excludeDying= */ false, /* excludePreCreated= */ !all);
if (users == null) {
pw.println("Error: couldn't get users");
return 1;
} else {
final int size = users.size();
int currentUser = UserHandle.USER_NULL;
if (verbose) {
pw.printf("%d users:\n\n", size);
currentUser = am.getCurrentUser().id;
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
// verbose option.
pw.println("Users:");
}
for (int i = 0; i < size; i++) {
final UserInfo user = users.get(i);
final boolean running = am.isUserRunning(user.id, 0);
if (verbose) {
final DevicePolicyManagerInternal dpm = LocalServices
.getService(DevicePolicyManagerInternal.class);
String deviceOwner = "";
String profileOwner = "";
if (dpm != null) {
final long identity = Binder.clearCallingIdentity();
// NOTE: dpm methods below CANNOT be called while holding the mUsersLock
try {
if (dpm.getDeviceOwnerUserId() == user.id) {
deviceOwner = " (device-owner)";
}
if (dpm.getProfileOwnerAsUser(user.id) != null) {
profileOwner = " (profile-owner)";
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
final boolean current = user.id == currentUser;
final boolean hasParent = user.profileGroupId != user.id
&& user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
final boolean visible = mService.isUserVisible(user.id);
pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n",
i,
user.id,
user.name,
user.userType.replace("android.os.usertype.", ""),
UserInfo.flagsToString(user.flags),
hasParent ? " (parentId=" + user.profileGroupId + ")" : "",
running ? " (running)" : "",
user.partial ? " (partial)" : "",
user.preCreated ? " (pre-created)" : "",
user.convertedFromPreCreated ? " (converted)" : "",
deviceOwner, profileOwner,
current ? " (current)" : "",
visible ? " (visible)" : ""
);
} else {
// NOTE: the standard "list users" command is used by integration tests and
// hence should not be changed. If you need to add more info, use the
// verbose option.
pw.printf("\t%s%s\n", user, running ? " running" : "");
}
}
return 0;
}
}
private int runReportPackageAllowlistProblems() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
boolean criticalOnly = false;
int mode = UserSystemPackageInstaller.USER_TYPE_PACKAGE_WHITELIST_MODE_NONE;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
case "--critical-only":
criticalOnly = true;
break;
case "--mode":
mode = Integer.parseInt(getNextArgRequired());
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
Slog.d(LOG_TAG, "runReportPackageAllowlistProblems(): verbose=" + verbose
+ ", criticalOnly=" + criticalOnly
+ ", mode=" + UserSystemPackageInstaller.modeToString(mode));
try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
mSystemPackageInstaller.dumpPackageWhitelistProblems(ipw, mode, verbose,
criticalOnly);
}
return 0;
}
private int runSetSystemUserModeEmulation() {
if (!confirmBuildIsDebuggable() || !confirmIsCalledByRoot()) {
return -1;
}
final PrintWriter pw = getOutPrintWriter();
// The headless system user cannot be locked; in theory, we could just make this check
// when going full -> headless, but it doesn't hurt to check on both (and it makes the
// code simpler)
if (mLockPatternUtils.isSecure(UserHandle.USER_SYSTEM)) {
pw.println("Cannot change system user mode when it has a credential");
return -1;
}
boolean restart = true;
boolean reboot = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--reboot":
reboot = true;
break;
case "--no-restart":
restart = false;
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
if (reboot && !restart) {
getErrPrintWriter().println("You can use --reboot or --no-restart, but not both");
return -1;
}
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:
getErrPrintWriter().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.SYSTEM_USER_MODE_EMULATION_PROPERTY, mode);
SystemProperties.set(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY, mode);
if (reboot) {
Slog.i(LOG_TAG, "Rebooting to finalize the changes");
pw.println("Rebooting to finalize changes");
UiThread.getHandler()
.post(() -> ShutdownThread.reboot(
ActivityThread.currentActivityThread().getSystemUiContext(),
"To switch headless / full system user mode",
/* confirm= */ false));
} else if (restart) {
Slog.i(LOG_TAG, "Shutting PackageManager down");
LocalServices.getService(PackageManagerInternal.class).shutdown();
final IActivityManager am = ActivityManager.getService();
if (am != null) {
try {
Slog.i(LOG_TAG, "Shutting ActivityManager down");
am.shutdown(/* timeout= */ 10_000);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Failed to shut down ActivityManager" + e);
}
}
final int pid = Process.myPid();
Slogf.i(LOG_TAG, "Restarting Android runtime(PID=%d) to finalize changes", pid);
pw.println("Restarting Android runtime to finalize changes");
pw.println("The restart may trigger a 'Broken pipe' message; this is to be expected.");
pw.flush();
// Ideally there should be a cleaner / safer option to restart system_server, but
// that doesn't seem to be the case. For example, ShutdownThread.reboot() calls
// pm.shutdown() and am.shutdown() (which we already are calling above), but when
// the system is restarted through 'adb shell stop && adb shell start`, these
// methods are not called, so just killing the process seems to be fine.
Process.killProcess(pid);
} else {
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)");
}
return 0;
}
@RequiresPermission(anyOf = {
"android.permission.INTERACT_ACROSS_USERS",
"android.permission.INTERACT_ACROSS_USERS_FULL"
})
private int runIsUserVisible() {
PrintWriter pw = getOutPrintWriter();
Integer displayId = null;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--display":
displayId = Integer.parseInt(getNextArgRequired());
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
int userId = UserHandle.parseUserArg(getNextArgRequired());
switch (userId) {
case UserHandle.USER_ALL:
case UserHandle.USER_CURRENT_OR_SELF:
case UserHandle.USER_NULL:
pw.printf("invalid value (%d) for --user option\n", userId);
return -1;
case UserHandle.USER_CURRENT:
userId = ActivityManager.getCurrentUser();
break;
}
boolean isVisible;
if (displayId != null) {
isVisible = mService.isUserVisibleOnDisplay(userId, displayId);
} else {
isVisible = getUserManagerForUser(userId).isUserVisible();
}
// NOTE: do not change output below (or command name / args), as it's used by ITestDevice
pw.println(isVisible);
return 0;
}
private int runIsHeadlessSystemUserMode() {
PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
boolean effective = mService.isHeadlessSystemUserMode();
if (!verbose) {
// NOTE: do not change output below, as it's used by ITestDevice
// (it's ok to change the verbose option though)
pw.println(effective);
} else {
pw.printf("effective=%b real=%b\n", effective,
RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER);
}
return 0;
}
private int runIsVisibleBackgroundUserSupported() {
PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
boolean effective = UserManager.isVisibleBackgroundUsersEnabled();
if (!verbose) {
// NOTE: do not change output below, as it's used by ITestDevice
// (it's ok to change the verbose option though)
pw.println(effective);
} else {
pw.printf("effective=%b real=%b\n", effective, Resources.getSystem()
.getBoolean(R.bool.config_multiuserVisibleBackgroundUsers));
}
return 0;
}
private int runIsVisibleBackgroundUserOnDefaultDisplaySupported() {
PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
case "--verbose":
verbose = true;
break;
default:
pw.println("Invalid option: " + opt);
return -1;
}
}
boolean effective = UserManager.isVisibleBackgroundUsersOnDefaultDisplayEnabled();
if (!verbose) {
// NOTE: do not change output below, as it's used by ITestDevice
// (it's ok to change the verbose option though)
pw.println(effective);
} else {
pw.printf("effective=%b real=%b\n", effective, Resources.getSystem()
.getBoolean(R.bool.config_multiuserVisibleBackgroundUsersOnDefaultDisplay));
}
return 0;
}
private int runGetMainUserId() {
PrintWriter pw = getOutPrintWriter();
final int mainUserId = mService.getMainUserId();
if (mainUserId == UserHandle.USER_NULL) {
pw.println("None");
return 1;
}
pw.println(mainUserId);
return 0;
}
private int canSwitchToHeadlessSystemUser() {
PrintWriter pw = getOutPrintWriter();
boolean canSwitchToHeadlessSystemUser = mService.canSwitchToHeadlessSystemUser();
pw.println(canSwitchToHeadlessSystemUser);
return 0;
}
private int isMainUserPermanentAdmin() {
PrintWriter pw = getOutPrintWriter();
boolean isMainUserPermanentAdmin = mService.isMainUserPermanentAdmin();
pw.println(isMainUserPermanentAdmin);
return 0;
}
/**
* Gets the {@link UserManager} associated with the context of the given user.
*/
private UserManager getUserManagerForUser(int userId) {
UserHandle user = UserHandle.of(userId);
Context context = mContext.createContextAsUser(user, /* flags= */ 0);
return context.getSystemService(UserManager.class);
}
/**
* 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;
}
}