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

Commit 4264560e authored by Felipe Leme's avatar Felipe Leme
Browse files

Initial implementation of "start bg user on secondary display".

This API will be used on automotives that support multiple displays
to start a passenger user on secondary displays. Once a user is
started this way, it will be "visible"
(i.e. UserManager.isUserVisible() will return true).

This CL provides the very minimum support to such API
(A.K.A. "My Dog, it's full of TODOs!" :-), so it can unblock other
efforts (like changing WindowManager to allow activities to be
launched).

Bug: 239982558

Test: adb shell am start-user --display 42 10
Test: adb shell dumpsys user | egrep '(Users on secondary|isUserVisible)'

Change-Id: I21dc85720da7d0adc24ca9f4573b85ef5a380239
parent d05c70a2
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -760,4 +760,15 @@ interface IActivityManager {
     * </p>
     */
    int getBackgroundRestrictionExemptionReason(int uid);

    // Start (?) of T transactions
    /**
     * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
     * but setting the user as the visible user of that display (i.e., allowing the user and its
     * running profiles to launch activities on that display).
     *
     * <p>Typically used only by automotive builds when the vehicle has multiple displays.
     */
    boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId, IProgressListener unlockProgressListener);

}
+7 −0
Original line number Diff line number Diff line
@@ -16196,6 +16196,13 @@ public class ActivityManagerService extends IActivityManager.Stub
        return mUserController.startUser(userId, /* foreground */ true, unlockListener);
    }
    @Override
    public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId,
            @Nullable IProgressListener unlockListener) {
        // Permission check done inside UserController.
        return mUserController.startUserOnSecondaryDisplay(userId, displayId, unlockListener);
    }
    /**
     * Unlocks the given user.
     *
+38 −13
Original line number Diff line number Diff line
@@ -891,6 +891,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
        }
    }

    // TODO(b/239982558): might need to support --displayId as well
    private int runProfile(PrintWriter pw) throws RemoteException {
        final PrintWriter err = getErrPrintWriter();
        String profileFile = null;
@@ -2028,10 +2029,16 @@ final class ActivityManagerShellCommand extends ShellCommand {
    int runStartUser(PrintWriter pw) throws RemoteException {
        boolean wait = false;
        String opt;
        int displayId = Display.INVALID_DISPLAY;
        while ((opt = getNextOption()) != null) {
            if ("-w".equals(opt)) {
            switch(opt) {
                case "-w":
                    wait = true;
            } else {
                    break;
                case "--display":
                    displayId = getDisplayIdFromNextArg();
                    break;
                default:
                    getErrPrintWriter().println("Error: unknown option: " + opt);
                    return -1;
            }
@@ -2039,15 +2046,25 @@ final class ActivityManagerShellCommand extends ShellCommand {
        int userId = Integer.parseInt(getNextArgRequired());

        final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
        boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);

        boolean success;
        String displaySuffix;

        if (displayId == Display.INVALID_DISPLAY) {
            success = mInterface.startUserInBackgroundWithListener(userId, waiter);
            displaySuffix = "";
        } else {
            success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId, waiter);
            displaySuffix = " on display " + displayId;
        }
        if (wait && success) {
            success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
        }

        if (success) {
            pw.println("Success: user started");
            pw.println("Success: user started" + displaySuffix);
        } else {
            getErrPrintWriter().println("Error: could not start user");
            getErrPrintWriter().println("Error: could not start user" + displaySuffix);
        }
        return 0;
    }
@@ -2500,6 +2517,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
        }
    }

    private int getDisplayIdFromNextArg() {
        int displayId = Integer.parseInt(getNextArgRequired());
        if (displayId < 0) {
            throw new IllegalArgumentException("--display must be a non-negative integer");
        }
        return displayId;
    }

    int runGetConfig(PrintWriter pw) throws RemoteException {
        int days = -1;
        int displayId = Display.DEFAULT_DISPLAY;
@@ -2518,10 +2543,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
            } else if (opt.equals("--device")) {
                inclDevice = true;
            } else if (opt.equals("--display")) {
                displayId = Integer.parseInt(getNextArgRequired());
                if (displayId < 0) {
                    throw new IllegalArgumentException("--display must be a non-negative integer");
                }
                displayId = getDisplayIdFromNextArg();
            } else {
                getErrPrintWriter().println("Error: Unknown option: " + opt);
                return -1;
@@ -3708,10 +3730,13 @@ final class ActivityManagerShellCommand extends ShellCommand {
            pw.println("      execution of that user if it is currently stopped.");
            pw.println("  get-current-user");
            pw.println("      Returns id of the current foreground user.");
            pw.println("  start-user [-w] <USER_ID>");
            pw.println("  start-user [-w] [--display DISPLAY_ID] <USER_ID>");
            pw.println("      Start USER_ID in background if it is currently stopped;");
            pw.println("      use switch-user if you want to start the user in foreground.");
            pw.println("      -w: wait for start-user to complete and the user to be unlocked.");
            pw.println("      --display <DISPLAY_ID>: allows the user to launch activities in the");
            pw.println("        given display, when supported (typically on automotive builds");
            pw.println("        wherethe vehicle has multiple displays)");
            pw.println("  unlock-user <USER_ID>");
            pw.println("      Unlock the given user.  This will only work if the user doesn't");
            pw.println("      have an LSKF (PIN/pattern/password).");
+47 −7
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -107,6 +108,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -1071,6 +1073,12 @@ class UserController implements Handler.Callback {
                        Binder.getCallingPid(), UserHandle.USER_ALL);
            });
        }

        // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
        // we'll need to remove this call and handle that as part of the user state workflow
        // instead.
        // TODO(b/240613396) also check if multi-display is supported
        mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
    }

    private void finishUserStopping(final int userId, final UserState uss,
@@ -1363,6 +1371,7 @@ class UserController implements Handler.Callback {
        }
        final int profilesToStartSize = profilesToStart.size();
        int i = 0;
        // TODO(b/239982558): pass displayId
        for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
            startUser(profilesToStart.get(i).id, /* foreground= */ false);
        }
@@ -1371,6 +1380,7 @@ class UserController implements Handler.Callback {
        }
    }

    // TODO(b/239982558): might need to infer the display id based on parent user
    /**
     * Starts a user only if it's a profile, with a more relaxed permission requirement:
     * {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1409,8 @@ class UserController implements Handler.Callback {
            return false;
        }

        return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
                /* unlockListener= */ null);
    }

    @VisibleForTesting
@@ -1445,26 +1456,54 @@ class UserController implements Handler.Callback {
            @Nullable IProgressListener unlockListener) {
        checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");

        return startUserNoChecks(userId, foreground, unlockListener);
        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
    }

    // TODO(b/239982558): add javadoc
    boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
            @Nullable IProgressListener unlockListener) {
        checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUserOnSecondaryDisplay");

        return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
    }

    private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
    private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
            @Nullable IProgressListener unlockListener) {
        TimingsTraceAndSlog t = new TimingsTraceAndSlog();

        t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
        t.traceBegin("UserController.startUser-" + userId
                + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
                + "-" + (foreground ? "fg" : "bg"));
        try {
            return startUserInternal(userId, foreground, unlockListener, t);
            return startUserInternal(userId, displayId, foreground, unlockListener, t);
        } finally {
            t.traceEnd();
        }
    }

    private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
    private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
            @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
        if (DEBUG_MU) {
            Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
            Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
                    foreground ? " in foreground" : "");
        }

        // TODO(b/240613396) also check if multi-display is supported
        if (displayId != Display.DEFAULT_DISPLAY) {
            // TODO(b/239982558): add unit test for the exceptional cases below
            Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
            Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
                    + " on secondary display (%d)", displayId);
            Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
                    + "on secondary display (%d)", userId, displayId);

            // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
            // we'll need to remove this call and handle that as part of the user state workflow
            // instead.
            // TODO(b/239982558): check if display is valid
            mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
        }

        EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);

        final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1558,7 @@ class UserController implements Handler.Callback {
                return false;
            }

            // TODO(b/239982558): might need something similar for bg users on secondary display
            if (foreground && isUserSwitchUiEnabled()) {
                t.traceBegin("startFreezingScreen");
                mInjector.getWindowManager().startFreezingScreen(
+3 −0
Original line number Diff line number Diff line
@@ -305,4 +305,7 @@ public abstract class UserManagerInternal {
     * for users that already existed on-disk from an older version of Android.
     */
    public abstract boolean shouldIgnorePrepareStorageErrors(int userId);

    /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
    public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
}
Loading