summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/app/ActivityManager.java35
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/os/UserManager.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java39
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java155
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",