diff options
8 files changed, 186 insertions, 56 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index ddbccfa8f404..bb9345f19b8f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -131,6 +131,7 @@ package android.app { method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int); + method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); @@ -1874,6 +1875,7 @@ package android.os { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle); method public static boolean isSplitSystemUser(); + method public static boolean isUsersOnSecondaryDisplaysEnabled(); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 449729e18376..182b0a3e5149 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4336,13 +4336,42 @@ public class ActivityManager { @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean switchUser(@NonNull UserHandle user) { - if (user == null) { - throw new IllegalArgumentException("UserHandle cannot be null."); - } + Preconditions.checkNotNull(user, "UserHandle cannot be null."); + return switchUser(user.getIdentifier()); } /** + * Starts the given user in background and associate the user with 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()}). + * + * @return whether the user was started. + * + * @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 + */ + @TestApi + @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.CREATE_USERS}) + public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId, + int displayId) { + try { + return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Gets the message that is shown when a user is switched from. * * @hide diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index ec05b46f7785..bd999fc04844 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -769,6 +769,6 @@ interface IActivityManager { * * <p>Typically used only by automotive builds when the vehicle has multiple displays. */ - boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId, IProgressListener unlockProgressListener); + boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c802e56c8dc7..7a85affb4d28 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2840,6 +2840,7 @@ public class UserManager { /** * @hide */ + @TestApi public static boolean isUsersOnSecondaryDisplaysEnabled() { return SystemProperties.getBoolean("fw.users_on_secondary_displays", Resources.getSystem() diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 697c6a97466a..e4ddecff5cb8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16197,10 +16197,10 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId, - @Nullable IProgressListener unlockListener) { + public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) { // Permission check done inside UserController. - return mUserController.startUserOnSecondaryDisplay(userId, displayId, unlockListener); + return mUserController.startUserOnSecondaryDisplay(userId, displayId, + /* unlockListener= */ null); } /** diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 2d9d1329ca31..aab6fc17fc1a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -2054,7 +2054,7 @@ final class ActivityManagerShellCommand extends ShellCommand { success = mInterface.startUserInBackgroundWithListener(userId, waiter); displaySuffix = ""; } else { - success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId, waiter); + success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId); displaySuffix = " on display " + displayId; } if (wait && success) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 3dab27bed8a1..1edfabed7ad1 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -16,9 +16,11 @@ package com.android.server.am; +import static android.Manifest.permission.CREATE_USERS; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_USERS; import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT; import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE; import static android.app.ActivityManager.StopUserOnSwitch; @@ -1459,10 +1461,12 @@ class UserController implements Handler.Callback { return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener); } - // TODO(b/239982558): add javadoc + // TODO(b/239982558): add javadoc (need to wait until the intents / SystemService callbacks are + // defined boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId, @Nullable IProgressListener unlockListener) { - checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUserOnSecondaryDisplay"); + checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay", + MANAGE_USERS, CREATE_USERS); return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener); } @@ -1488,8 +1492,12 @@ class UserController implements Handler.Callback { foreground ? " in foreground" : ""); } - // TODO(b/240613396) also check if multi-display is supported if (displayId != Display.DEFAULT_DISPLAY) { + if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) { + // TODO(b/239824814): add CTS test and/or unit test + throw new UnsupportedOperationException("Not supported by device"); + } + // TODO(b/239982558): add unit test for the exceptional cases below Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId); Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user" @@ -2608,15 +2616,24 @@ class UserController implements Handler.Callback { } private void checkCallingPermission(String permission, String methodName) { - if (mInjector.checkCallingPermission(permission) - != PackageManager.PERMISSION_GRANTED) { - String msg = "Permission denial: " + methodName - + "() from pid=" + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " requires " + permission; - Slogf.w(TAG, msg); - throw new SecurityException(msg); + checkCallingHasOneOfThosePermissions(methodName, permission); + } + + private void checkCallingHasOneOfThosePermissions(String methodName, String...permissions) { + for (String permission : permissions) { + if (mInjector.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) { + return; + } } + String msg = "Permission denial: " + methodName + + "() from pid=" + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + + (permissions.length == 1 + ? permissions[0] + : "one of " + Arrays.toString(permissions)); + Slogf.w(TAG, msg); + throw new SecurityException(msg); } private void enforceShellRestriction(String restriction, @UserIdInt int userId) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 48592f09e4d7..cc7abdc20844 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -97,7 +97,6 @@ import android.util.ArraySet; import android.util.AtomicFile; import android.util.IndentingPrintWriter; import android.util.IntArray; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -622,6 +621,16 @@ public class UserManagerService extends IUserManager.Stub { @GuardedBy("mUserStates") private final WatchedUserStates mUserStates = new WatchedUserStates(); + /** + * Used on devices that support background users (key) running on secondary displays (value). + * + * <p>Is {@code null} by default and instantiated on demand when the users are started on + * secondary displays. + */ + @Nullable + @GuardedBy("mUsersLock") + private SparseIntArray mUsersOnSecondaryDisplays; + private static UserManagerService sInstance; public static UserManagerService getInstance() { @@ -692,11 +701,6 @@ public class UserManagerService extends IUserManager.Stub { } } - // TODO(b/239982558): add javadoc - @Nullable - @GuardedBy("mUsersLock") - private SparseIntArray mUsersOnSecondaryDisplays; - // TODO b/28848102 Add support for test dependencies injection @VisibleForTesting UserManagerService(Context context) { @@ -1670,15 +1674,33 @@ public class UserManagerService extends IUserManager.Stub { + ") is visible"); } - // First check current foreground user (on main display) + return isUserVisibleUnchecked(userId); + } + + private boolean isUserVisibleUnchecked(@UserIdInt int userId) { + // First check current foreground user and their profiles (on main display) + if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) { + return true; + } + + // TODO(b/239824814): STOPSHIP - add CTS tests (requires change on testing infra) + synchronized (mUsersLock) { + if (mUsersOnSecondaryDisplays != null) { + // TODO(b/239824814): make sure it handles profile as well + return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0); + } + } + + return false; + } + + private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) { int currentUserId = Binder.withCleanCallingIdentity(() -> ActivityManager.getCurrentUser()); if (currentUserId == userId) { return true; } - // Then profiles of current user - // TODO(b/239824814): consider using AMInternal.isCurrentProfile() instead if (isProfile(userId)) { int parentId = Binder.withCleanCallingIdentity(() -> getProfileParentId(userId)); if (parentId == currentUserId) { @@ -1686,15 +1708,22 @@ public class UserManagerService extends IUserManager.Stub { } } - // TODO(b/239824814): STOPSHIP - add unit tests (requires change on testing infra) + return false; + } + + // TODO(b/239982558): currently used just by shell command, might need to move to + // UserManagerInternal if needed by other components (like WindowManagerService) + // TODO(b/239982558): add unit test + // TODO(b/239982558): try to merge with isUserVisibleUnchecked() + private boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + return isCurrentUserOrRunningProfileOfCurrentUser(userId); + } synchronized (mUsersLock) { - if (mUsersOnSecondaryDisplays != null) { - // TODO(b/239824814): make sure it handles profile as well - return (mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0); - } + // TODO(b/239824814): make sure it handles profile as well + return mUsersOnSecondaryDisplays != null && mUsersOnSecondaryDisplays.get(userId, + Display.INVALID_DISPLAY) == displayId; } - - return false; } @Override @@ -5790,6 +5819,11 @@ public class UserManagerService extends IUserManager.Stub { pw.println(" --reboot (which does a full reboot) or"); pw.println(" --no-restart (which requires a manual restart)"); pw.println(); + pw.println(" is-user-visible [--display DISPLAY_ID] <USER_ID>"); + pw.println(" Checks if the given user is visible in the given display."); + pw.println(" If the display option is not set, it uses the user's context to check"); + pw.println(" (so it emulates what apps would get from UserManager.isUserVisible())"); + pw.println(); } @Override @@ -5806,6 +5840,8 @@ public class UserManagerService extends IUserManager.Stub { return runReportPackageAllowlistProblems(); case "set-system-user-mode-emulation": return runSetSystemUserModeEmulation(); + case "is-user-visible": + return runIsUserVisible(); default: return handleDefaultCommands(cmd); } @@ -5855,9 +5891,6 @@ public class UserManagerService extends IUserManager.Stub { for (int i = 0; i < size; i++) { final UserInfo user = users.get(i); final boolean running = am.isUserRunning(user.id, 0); - final boolean current = user.id == currentUser; - final boolean hasParent = user.profileGroupId != user.id - && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID; if (verbose) { final DevicePolicyManagerInternal dpm = getDevicePolicyManagerInternal(); String deviceOwner = ""; @@ -5876,7 +5909,11 @@ public class UserManagerService extends IUserManager.Stub { Binder.restoreCallingIdentity(ident); } } - pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s\n", + final boolean current = user.id == currentUser; + final boolean hasParent = user.profileGroupId != user.id + && user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID; + final boolean visible = isUserVisibleUnchecked(user.id); + pw.printf("%d: id=%d, name=%s, type=%s, flags=%s%s%s%s%s%s%s%s%s%s\n", i, user.id, user.name, @@ -5888,7 +5925,9 @@ public class UserManagerService extends IUserManager.Stub { user.preCreated ? " (pre-created)" : "", user.convertedFromPreCreated ? " (converted)" : "", deviceOwner, profileOwner, - current ? " (current)" : ""); + current ? " (current)" : "", + visible ? " (visible)" : "" + ); } else { // NOTE: the standard "list users" command is used by integration tests and // hence should not be changed. If you need to add more info, use the @@ -6045,6 +6084,56 @@ public class UserManagerService extends IUserManager.Stub { return 0; } + private int runIsUserVisible() { + PrintWriter pw = getOutPrintWriter(); + int displayId = Display.INVALID_DISPLAY; + String opt; + while ((opt = getNextOption()) != null) { + boolean invalidOption = false; + switch (opt) { + case "--display": + displayId = Integer.parseInt(getNextArgRequired()); + invalidOption = displayId == Display.INVALID_DISPLAY; + break; + default: + invalidOption = true; + } + if (invalidOption) { + pw.println("Invalid option: " + opt); + return -1; + } + } + int userId = UserHandle.parseUserArg(getNextArgRequired()); + switch (userId) { + case UserHandle.USER_ALL: + case UserHandle.USER_CURRENT_OR_SELF: + case UserHandle.USER_NULL: + pw.printf("invalid value (%d) for --user option\n", userId); + return -1; + case UserHandle.USER_CURRENT: + userId = ActivityManager.getCurrentUser(); + break; + } + + boolean isVisible; + if (displayId != Display.INVALID_DISPLAY) { + isVisible = isUserVisibleOnDisplay(userId, displayId); + } else { + isVisible = getUserManagerForUser(userId).isUserVisible(); + } + pw.println(isVisible); + return 0; + } + + /** + * Gets the {@link UserManager} associated with the context of the given user. + */ + private UserManager getUserManagerForUser(int userId) { + UserHandle user = UserHandle.of(userId); + Context context = mContext.createContextAsUser(user, /* flags= */ 0); + return context.getSystemService(UserManager.class); + } + /** * Confirms if the build is debuggable * @@ -6144,18 +6233,17 @@ public class UserManagerService extends IUserManager.Stub { pw.print(" Cached user IDs (including pre-created): "); pw.println(Arrays.toString(mUserIdsIncludingPreCreated)); } - synchronized (mUsersLock) { - if (mUsersOnSecondaryDisplays != null) { - pw.print(" Users on secondary displays: "); - pw.println(mUsersOnSecondaryDisplays); - } - } - } // synchronized (mPackagesLock) // Multiple Users on Multiple Display info pw.println(" Supports users on secondary displays: " + UserManager.isUsersOnSecondaryDisplaysEnabled()); + synchronized (mUsersLock) { + if (mUsersOnSecondaryDisplays != null) { + pw.print(" Users on secondary displays: "); + pw.println(mUsersOnSecondaryDisplays); + } + } // Dump some capabilities pw.println(); @@ -6322,11 +6410,6 @@ public class UserManagerService extends IUserManager.Stub { pw.println(" Ignore errors preparing storage: " + userData.getIgnorePrepareStorageErrors()); - - // TODO(b/239824814): STOPSHIP - remove once there is a CTS test for UM.isUserVisible() - if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { - pw.printf(" isUserVisible(): %b\n", isUserVisible(userInfo.id)); - } } private static void dumpTimeAgo(PrintWriter pw, StringBuilder sb, long nowTime, long time) { @@ -6749,12 +6832,10 @@ public class UserManagerService extends IUserManager.Stub { // - displayId already assigned to another user synchronized (mUsersLock) { if (mUsersOnSecondaryDisplays == null) { - // TODO(b/240613396): get size from some config file - int size = 1; if (DBG) { - Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays with size %d", size); + Slogf.d(LOG_TAG, "Creating mUsersOnSecondaryDisplays"); } - mUsersOnSecondaryDisplays = new SparseIntArray(size); + mUsersOnSecondaryDisplays = new SparseIntArray(); } if (DBG) { Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays", |