Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 32853cb4 authored by Nikhil Kumar's avatar Nikhil Kumar Committed by Android (Google) Code Review
Browse files

Merge "Split ShellCommand implementation out of UserManagerService"

parents f3ba41bd 1b3c2a6d
Loading
Loading
Loading
Loading
+4 −380
Original line number Original line Diff line number Diff line
@@ -33,7 +33,6 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
import android.app.IActivityManager;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
import android.app.IStopUserCallback;
import android.app.KeyguardManager;
import android.app.KeyguardManager;
@@ -80,7 +79,6 @@ import android.os.SELinux;
import android.os.ServiceManager;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.os.ServiceSpecificException;
import android.os.ShellCallback;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserHandle;
@@ -127,11 +125,9 @@ import com.android.server.BundleUtils;
import com.android.server.LocalServices;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.LockGuard;
import com.android.server.SystemService;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.am.UserState;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
import com.android.server.power.ShutdownThread;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.storage.DeviceStorageMonitorInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.utils.TimingsTraceAndSlog;
@@ -1756,7 +1752,7 @@ public class UserManagerService extends IUserManager.Stub {
    // UserManagerInternal if needed by other components (like WindowManagerService)
    // UserManagerInternal if needed by other components (like WindowManagerService)
    // TODO(b/239982558): add unit test
    // TODO(b/239982558): add unit test
    // TODO(b/239982558): try to merge with isUserVisibleUnchecked()
    // 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) {
        if (displayId == Display.DEFAULT_DISPLAY) {
            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
        }
        }
@@ -5937,383 +5933,11 @@ public class UserManagerService extends IUserManager.Stub {
    public void onShellCommand(FileDescriptor in, FileDescriptor out,
    public void onShellCommand(FileDescriptor in, FileDescriptor out,
            FileDescriptor err, String[] args, ShellCallback callback,
            FileDescriptor err, String[] args, ShellCallback callback,
            ResultReceiver resultReceiver) {
            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
    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
        if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
+450 −0

File added.

Preview size limit exceeded, changes collapsed.