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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user