Merge "More minor improvements on users on secondary display APIs."

This commit is contained in:
TreeHugger Robot
2022-09-16 18:13:19 +00:00
committed by Android (Google) Code Review
5 changed files with 76 additions and 39 deletions

View File

@@ -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

View File

@@ -766,6 +766,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);
}

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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();