summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java384
-rw-r--r--services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java450
2 files changed, 454 insertions, 380 deletions
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e423ea9170d7..c7fff4d84167 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,7 +33,6 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
-import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
@@ -79,7 +78,6 @@ import android.os.SELinux;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.ShellCallback;
-import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -126,11 +124,9 @@ import com.android.server.BundleUtils;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
-import com.android.server.UiThread;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
-import com.android.server.power.ShutdownThread;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -1755,7 +1751,7 @@ public class UserManagerService extends IUserManager.Stub {
// UserManagerInternal if needed by other components (like WindowManagerService)
// TODO(b/239982558): add unit test
// TODO(b/239982558): try to merge with isUserVisibleUnchecked()
- private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+ boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
return isCurrentUserOrRunningProfileOfCurrentUser(userId);
}
@@ -5905,383 +5901,11 @@ public class UserManagerService extends IUserManager.Stub {
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
+ (new UserManagerServiceShellCommand(this, mSystemPackageInstaller,
+ mLockPatternUtils, mContext))
+ .exec(this, in, out, err, args, callback, resultReceiver);
}
- private final class Shell extends ShellCommand {
-
- @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-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();
- }
-
- @Override
- public int onCommand(String cmd) {
- if (cmd == null) {
- return handleDefaultCommands(cmd);
- }
-
- 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-user-visible":
- return runIsUserVisible();
- 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 = 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 = getDevicePolicyManagerInternal();
- String deviceOwner = "";
- String profileOwner = "";
- if (dpm != null) {
- final long ident = 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(ident);
- }
- }
- final boolean current = user.id == currentUser;
- final boolean hasParent = user.profileGroupId != user.id
- && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID;
- final boolean visible = isUserVisibleUnchecked(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");
- getPackageManagerInternal().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) {
- }
- }
-
- 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.flush();
-
- // Ideally there should be a cleaner / safer option to restart system_server, but
- // that doesn't seems 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;
- }
-
- private int runIsUserVisible() {
- PrintWriter pw = getOutPrintWriter();
- int displayId = Display.INVALID_DISPLAY;
- String opt;
- while ((opt = getNextOption()) != null) {
- boolean invalidOption = false;
- switch (opt) {
- case "--display":
- displayId = Integer.parseInt(getNextArgRequired());
- invalidOption = displayId == Display.INVALID_DISPLAY;
- break;
- default:
- invalidOption = true;
- }
- if (invalidOption) {
- 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 != Display.INVALID_DISPLAY) {
- isVisible = isUserVisibleOnDisplay(userId, displayId);
- } else {
- isVisible = getUserManagerForUser(userId).isUserVisible();
- }
- pw.println(isVisible);
- 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;
- }
- } // class Shell
-
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
diff --git a/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
new file mode 100644
index 000000000000..8f13f7aed50a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserManagerServiceShellCommand.java
@@ -0,0 +1,450 @@
+/*
+ * 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.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 android.view.Display;
+
+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-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();
+ }
+
+ @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-user-visible":
+ return runIsUserVisible();
+ 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.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();
+ int displayId = Display.INVALID_DISPLAY;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ boolean invalidOption = false;
+ switch (opt) {
+ case "--display":
+ displayId = Integer.parseInt(getNextArgRequired());
+ invalidOption = displayId == Display.INVALID_DISPLAY;
+ break;
+ default:
+ invalidOption = true;
+ }
+ if (invalidOption) {
+ 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 != Display.INVALID_DISPLAY) {
+ isVisible = mService.isUserVisibleOnDisplay(userId, displayId);
+ } else {
+ isVisible = getUserManagerForUser(userId).isUserVisible();
+ }
+ pw.println(isVisible);
+ 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;
+ }
+}