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

Commit 5cb4bf9d authored by Felipe Leme's avatar Felipe Leme
Browse files

More minor improvements on users on secondary display APIs.

- Removed warning on UserManagerService.getUserAssignedToDisplay().
- Improved javadocs on UserManagerInternal and ActivityManager.
- Explicitly throws a UnsupportedOperationException on
  ActivityManager.startUserInBackgroundOnSecondaryDisplay() when it's
  not supported (and add a CTS test for it)
- Don't allow user to be assigned to multiple displays.

Bug: 244644281
Test: atest CtsMultiUserTestCases:android.multiuser.cts.UserManagerTest#testStartUserInBackgroundOnSecondaryDisplay \
            MultipleUsersOnMultipleDisplaysTest:android.multiuser.cts.UserManagerTest#testStartUserInBackgroundOnSecondaryDisplay

Change-Id: I01375d70b746148f99c4a5ff1eea6faf83154a86
parent 07b5ffb2
Loading
Loading
Loading
Loading
+23 −7
Original line number Diff line number Diff line
@@ -4360,20 +4360,28 @@ public class ActivityManager {
    }

    /**
     * Starts the given user in background and associate the user with the given display.
     * Starts the given user in background and assign the user to the given display.
     *
     * <p>This method will allow the user to launch activities on that display, and it's typically
     * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
     * supported by calling {@link UserManager#isBackgroundUsersOnSecondaryDisplaysSupported()}).
     * supported by calling {@link UserManager#isUsersOnSecondaryDisplaysSupported()}).
     *
     * @return whether the user was started.
     * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
     * user before starting a new one, this method does not stop the previous user running in
     * background in the display, and it will return {@code false} in this case. It's up to the
     * caller to call {@link #stopUser(int, boolean)} before starting a new user.
     *
     * @param userId user to be started in the display. It will return {@code false} if the user is
     * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
     * does not exist.
     *
     * @param displayId id of the display, it must exist.
     *
     * @return whether the operation succeeded. Notice that if the user was already started in such
     * display before, it will return {@code false}.
     *
     * @throws UnsupportedOperationException if the device does not support background users on
     * secondary displays.
     * @throws IllegalArgumentException if the display does not exist.
     * @throws IllegalStateException if the user cannot be started on that display (for example, if
     * there's already a user using that display or if the user is already associated with other
     * display).
     *
     * @hide
     */
@@ -4382,6 +4390,10 @@ public class ActivityManager {
            android.Manifest.permission.CREATE_USERS})
    public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
            int displayId) {
        if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
            throw new UnsupportedOperationException(
                    "device does not support users on secondary displays");
        }
        try {
            return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
        } catch (RemoteException e) {
@@ -4542,6 +4554,10 @@ public class ActivityManager {
    /**
     * Stops the given {@code userId}.
     *
     * <p><b>NOTE:</b> on systems that support
     * {@link UserManager#isUsersOnSecondaryDisplaysSupported() background users on secondary
     * displays}, this method will also unassign the user from the display it was started on.
     *
     * @hide
     */
    @TestApi
+2 −0
Original line number Diff line number Diff line
@@ -769,6 +769,8 @@ interface IActivityManager {
     *
     * <p>Typically used only by automotive builds when the vehicle has multiple displays.
     */
    @JavaPassthrough(annotation=
            "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
    boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);

}
+18 −10
Original line number Diff line number Diff line
@@ -328,8 +328,10 @@ public abstract class UserManagerInternal {
     * <p>On most devices this call will be a no-op, but it will be used on devices that support
     * multiple users on multiple displays (like automotives with passenger displays).
     *
     * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is
     * started and it doesn't validate if the display exists.
     * <p><b>NOTE: </b>this method doesn't validate if the display exists, it's up to the caller to
     * check it. In fact, one of the intended clients for this method is
     * {@code DisplayManagerService}, which will call it when a virtual display is created (another
     * client is {@code UserController}, which will call it when a user is started).
     *
     */
    public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
@@ -340,8 +342,8 @@ public abstract class UserManagerInternal {
     * <p>On most devices this call will be a no-op, but it will be used on devices that support
     * multiple users on multiple displays (like automotives with passenger displays).
     *
     * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} when a user is
     * stopped.
     * <p><b>NOTE: </b>this method is meant to be used only by {@code UserController} (when a user
     * is stopped) and {@code DisplayManagerService} (when a virtual display is destroyed).
     */
    public abstract void unassignUserFromDisplay(@UserIdInt int userId);

@@ -361,11 +363,14 @@ public abstract class UserManagerInternal {
     * Returns the display id assigned to the user, or {@code Display.INVALID_DISPLAY} if the
     * user is not assigned to any display.
     *
     * <p>The current foreground user is associated with the
     * <p>The current foreground user and its running profiles are associated with the
     * {@link android.view.Display#DEFAULT_DISPLAY default display}, while other users would only be
     * assigned to a display if they were started with
     * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}. If the user is a profile
     * and is running, it's assigned to its parent display.
     * assigned to a display if a call to {@link #assignUserToDisplay(int, int)} is made for such
     * user / display combination (for example, if the user was started with
     * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController}
     * would make such call).
     *
     * <p>If the user is a profile and is running, it's assigned to its parent display.
     */
    public abstract int getDisplayAssignedToUser(@UserIdInt int userId);

@@ -375,8 +380,11 @@ public abstract class UserManagerInternal {
     * associated with the display.
     *
     * <p>The {@link android.view.Display#DEFAULT_DISPLAY default display} is always assigned to
     * the current foreground user, while other displays would be associated with the user that was
     * started with {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}.
     * the current foreground user, while other displays would only be associated with users through
     * a explicit {@link #assignUserToDisplay(int, int)} call with that user / display combination
     * (for example, if the user was started with
     * {@code ActivityManager.startUserInBackgroundOnSecondaryDisplay()}, {@code UserController}
     * would make such call).
     */
    public abstract @UserIdInt int getUserAssignedToDisplay(int displayId);
}
+16 −22
Original line number Diff line number Diff line
@@ -1785,18 +1785,10 @@ public class UserManagerService extends IUserManager.Stub {

    @VisibleForTesting
    int getUserAssignedToDisplay(int displayId) {
        if (displayId == Display.DEFAULT_DISPLAY) {
        if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
            return getCurrentUserId();
        }

        if (!mUsersOnSecondaryDisplaysEnabled) {
            int currentUserId = getCurrentUserId();
            Slogf.w(LOG_TAG, "getUsersAssignedToDisplay(%d) called with non-DEFAULT_DISPLAY on "
                    + "system that doesn't support that; returning current user (%d)", displayId,
                    currentUserId);
            return currentUserId;
        }

        synchronized (mUsersOnSecondaryDisplays) {
            for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
                if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
@@ -6844,20 +6836,22 @@ public class UserManagerService extends IUserManager.Stub {

                // Check if display is available
                for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
                    // Make sure display is not used by other users...
                    // TODO(b/240736142); currently, if a user was started in a display, it
                    // would need to be stopped first, so "switching" a user on secondary
                    // diplay requires 2 non-atomic operations (stop and start). Once this logic
                    // is refactored, it should be atomic.
                    if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
                        throw new IllegalStateException("Cannot assign " + userId + " to "
                                + "display " + displayId + " as it's already assigned to "
                                + "user " + mUsersOnSecondaryDisplays.keyAt(i));
                    }
                    // TODO(b/239982558) also check that user is not already assigned to other
                    // display (including 0). That would be harder to tested under CTS though
                    // (for example, would need to add a new AM method to start user in bg on
                    // main display), so it's better to test on unit tests
                    int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
                    int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
                    if (DBG_MUMD) {
                        Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
                                i, assignedUserId, assignedDisplayId);
                    }
                    if (displayId == assignedDisplayId) {
                        throw new IllegalStateException("Cannot assign user " + userId + " to "
                                + "display " + displayId + " because such display is already "
                                + "assigned to user " + assignedUserId);
                    }
                    if (userId == assignedUserId) {
                        throw new IllegalStateException("Cannot assign user " + userId + " to "
                                + "display " + displayId + " because such user is as already "
                                + "assigned to display " + assignedDisplayId);
                    }
                }

                if (DBG_MUMD) {
+17 −0
Original line number Diff line number Diff line
@@ -162,6 +162,23 @@ public final class UserManagerInternalTest extends UserManagerServiceOrInternalT
                        + USER_ID + ".*");
    }

    @Test
    public void testAssignUserToDisplay_userAlreadyAssigned() {
        enableUsersOnSecondaryDisplays();

        mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);

        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));

        Log.v(TAG, "Exception: " + e);
        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
                .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
                        + SECONDARY_DISPLAY_ID + ".*");

        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
    }

    @Test
    public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
        enableUsersOnSecondaryDisplays();