Loading core/java/android/app/ActivityManager.java +23 −7 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -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) { Loading Loading @@ -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 Loading core/java/android/app/IActivityManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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); } services/core/java/com/android/server/pm/UserManagerInternal.java +18 −10 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); Loading @@ -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); Loading @@ -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); } services/core/java/com/android/server/pm/UserManagerService.java +16 −22 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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) { Loading services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading
core/java/android/app/ActivityManager.java +23 −7 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -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) { Loading Loading @@ -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 Loading
core/java/android/app/IActivityManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -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); }
services/core/java/com/android/server/pm/UserManagerInternal.java +18 −10 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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); Loading @@ -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); Loading @@ -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); }
services/core/java/com/android/server/pm/UserManagerService.java +16 −22 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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) { Loading
services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java +17 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading