diff options
5 files changed, 76 insertions, 39 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 8f5457abdca4..dfef279be7c5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -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 diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index bd999fc04844..643962a6a9c3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -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); } diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java index 4f5fd023d470..b62024996424 100644 --- a/services/core/java/com/android/server/pm/UserManagerInternal.java +++ b/services/core/java/com/android/server/pm/UserManagerInternal.java @@ -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); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 07ec80bcd603..3b3e1db0f35d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -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)); + 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); } - // 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 } if (DBG_MUMD) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java index 574bab2d9962..245b4dcb25cb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java @@ -163,6 +163,23 @@ public final class UserManagerInternalTest extends UserManagerServiceOrInternalT } @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(); addDefaultProfileAndParent(); |