summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityManager.java30
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java28
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java38
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java17
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();