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

Commit 1b3c2a6d authored by Nikhil Kumar's avatar Nikhil Kumar
Browse files

Split ShellCommand implementation out of UserManagerService

Moved the shellCommand internal implementation from UserManagerService
to an outer Class UserManagerServiceShellCommand.

Bug: 241245829
Test: adb shell cmd user list
Test: adb shell cmd user report-system-user-package-whitelist-problem
Test: adb shell cmd user set-system-user-mode-emulation --no-restart default
Test: adb shell cmd user is-user-visible 0 cur
BYPASS_INCLUSIVE_LANGUAGE_REASON: existing API
Change-Id: I40e24eb6ee787549540626b3ea698b055d775821
parent f8da4974
Loading
Loading
Loading
Loading
+4 −380
Original line number Diff line number Diff line
@@ -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;
+450 −0

File added.

Preview size limit exceeded, changes collapsed.