summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/IActivityManager.aidl11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java51
-rw-r--r--services/core/java/com/android/server/am/UserController.java54
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java79
6 files changed, 184 insertions, 21 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 8367441b1b95..ec05b46f7785 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -760,4 +760,15 @@ interface IActivityManager {
* </p>
*/
int getBackgroundRestrictionExemptionReason(int uid);
+
+ // Start (?) of T transactions
+ /**
+ * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+ * but setting the user as the visible user of that display (i.e., allowing the user and its
+ * running profiles to launch activities on that display).
+ *
+ * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+ */
+ boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId, IProgressListener unlockProgressListener);
+
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7f1cf0339460..697c6a97466a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16196,6 +16196,13 @@ public class ActivityManagerService extends IActivityManager.Stub
return mUserController.startUser(userId, /* foreground */ true, unlockListener);
}
+ @Override
+ public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId,
+ @Nullable IProgressListener unlockListener) {
+ // Permission check done inside UserController.
+ return mUserController.startUserOnSecondaryDisplay(userId, displayId, unlockListener);
+ }
+
/**
* Unlocks the given user.
*
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index dbabe99c79d5..2d9d1329ca31 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -891,6 +891,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
+ // TODO(b/239982558): might need to support --displayId as well
private int runProfile(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
String profileFile = null;
@@ -2028,26 +2029,42 @@ final class ActivityManagerShellCommand extends ShellCommand {
int runStartUser(PrintWriter pw) throws RemoteException {
boolean wait = false;
String opt;
+ int displayId = Display.INVALID_DISPLAY;
while ((opt = getNextOption()) != null) {
- if ("-w".equals(opt)) {
- wait = true;
- } else {
- getErrPrintWriter().println("Error: unknown option: " + opt);
- return -1;
+ switch(opt) {
+ case "-w":
+ wait = true;
+ break;
+ case "--display":
+ displayId = getDisplayIdFromNextArg();
+ break;
+ default:
+ getErrPrintWriter().println("Error: unknown option: " + opt);
+ return -1;
}
}
int userId = Integer.parseInt(getNextArgRequired());
final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
- boolean success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+
+ boolean success;
+ String displaySuffix;
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ success = mInterface.startUserInBackgroundWithListener(userId, waiter);
+ displaySuffix = "";
+ } else {
+ success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId, waiter);
+ displaySuffix = " on display " + displayId;
+ }
if (wait && success) {
success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
}
if (success) {
- pw.println("Success: user started");
+ pw.println("Success: user started" + displaySuffix);
} else {
- getErrPrintWriter().println("Error: could not start user");
+ getErrPrintWriter().println("Error: could not start user" + displaySuffix);
}
return 0;
}
@@ -2500,6 +2517,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
}
+ private int getDisplayIdFromNextArg() {
+ int displayId = Integer.parseInt(getNextArgRequired());
+ if (displayId < 0) {
+ throw new IllegalArgumentException("--display must be a non-negative integer");
+ }
+ return displayId;
+ }
+
int runGetConfig(PrintWriter pw) throws RemoteException {
int days = -1;
int displayId = Display.DEFAULT_DISPLAY;
@@ -2518,10 +2543,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
} else if (opt.equals("--device")) {
inclDevice = true;
} else if (opt.equals("--display")) {
- displayId = Integer.parseInt(getNextArgRequired());
- if (displayId < 0) {
- throw new IllegalArgumentException("--display must be a non-negative integer");
- }
+ displayId = getDisplayIdFromNextArg();
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
@@ -3708,10 +3730,13 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" execution of that user if it is currently stopped.");
pw.println(" get-current-user");
pw.println(" Returns id of the current foreground user.");
- pw.println(" start-user [-w] <USER_ID>");
+ pw.println(" start-user [-w] [--display DISPLAY_ID] <USER_ID>");
pw.println(" Start USER_ID in background if it is currently stopped;");
pw.println(" use switch-user if you want to start the user in foreground.");
pw.println(" -w: wait for start-user to complete and the user to be unlocked.");
+ pw.println(" --display <DISPLAY_ID>: allows the user to launch activities in the");
+ pw.println(" given display, when supported (typically on automotive builds");
+ pw.println(" wherethe vehicle has multiple displays)");
pw.println(" unlock-user <USER_ID>");
pw.println(" Unlock the given user. This will only work if the user doesn't");
pw.println(" have an LSKF (PIN/pattern/password).");
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 0c0ae7d8f934..3dab27bed8a1 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,6 +100,7 @@ import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -107,6 +108,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FactoryResetter;
import com.android.server.FgThread;
@@ -1071,6 +1073,12 @@ class UserController implements Handler.Callback {
Binder.getCallingPid(), UserHandle.USER_ALL);
});
}
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/240613396) also check if multi-display is supported
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, Display.INVALID_DISPLAY);
}
private void finishUserStopping(final int userId, final UserState uss,
@@ -1363,6 +1371,7 @@ class UserController implements Handler.Callback {
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
+ // TODO(b/239982558): pass displayId
for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
@@ -1371,6 +1380,7 @@ class UserController implements Handler.Callback {
}
}
+ // TODO(b/239982558): might need to infer the display id based on parent user
/**
* Starts a user only if it's a profile, with a more relaxed permission requirement:
* {@link android.Manifest.permission#MANAGE_USERS} or
@@ -1399,7 +1409,8 @@ class UserController implements Handler.Callback {
return false;
}
- return startUserNoChecks(userId, /* foreground= */ false, /* unlockListener= */ null);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
+ /* unlockListener= */ null);
}
@VisibleForTesting
@@ -1445,26 +1456,54 @@ class UserController implements Handler.Callback {
@Nullable IProgressListener unlockListener) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
- return startUserNoChecks(userId, foreground, unlockListener);
+ return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
+ }
+
+ // TODO(b/239982558): add javadoc
+ boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId,
+ @Nullable IProgressListener unlockListener) {
+ checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUserOnSecondaryDisplay");
+
+ return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
}
- private boolean startUserNoChecks(final @UserIdInt int userId, final boolean foreground,
+ private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("UserController.startUser-" + userId + "-" + (foreground ? "fg" : "bg"));
+ t.traceBegin("UserController.startUser-" + userId
+ + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
+ + "-" + (foreground ? "fg" : "bg"));
try {
- return startUserInternal(userId, foreground, unlockListener, t);
+ return startUserInternal(userId, displayId, foreground, unlockListener, t);
} finally {
t.traceEnd();
}
}
- private boolean startUserInternal(@UserIdInt int userId, boolean foreground,
+ private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d%s", userId, foreground ? " in foreground" : "");
+ Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ foreground ? " in foreground" : "");
}
+
+ // TODO(b/240613396) also check if multi-display is supported
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ // 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"
+ + " on secondary display (%d)", displayId);
+ Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ + "on secondary display (%d)", userId, displayId);
+
+ // TODO(b/239982558): for now we're just updating the user's visibility, but most likely
+ // we'll need to remove this call and handle that as part of the user state workflow
+ // instead.
+ // TODO(b/239982558): check if display is valid
+ mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
+ }
+
EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
final int callingUid = Binder.getCallingUid();
@@ -1519,6 +1558,7 @@ class UserController implements Handler.Callback {
return false;
}
+ // TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 8dc94287688d..d358e43c6762 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -305,4 +305,7 @@ public abstract class UserManagerInternal {
* for users that already existed on-disk from an older version of Android.
*/
public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+
+ /** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
+ public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a0335e89ceec..48592f09e4d7 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,6 +97,7 @@ 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;
@@ -107,6 +108,7 @@ import android.util.TypedValue;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
+import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -690,6 +692,11 @@ 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) {
@@ -1679,7 +1686,14 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- // TODO(b/239824814): support background users on secondary display (and their profiles)
+ // TODO(b/239824814): STOPSHIP - add unit 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;
}
@@ -6130,6 +6144,12 @@ 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)
@@ -6302,6 +6322,11 @@ 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) {
@@ -6686,6 +6711,58 @@ public class UserManagerService extends IUserManager.Stub {
return userData != null && userData.getIgnorePrepareStorageErrors();
}
}
+
+ @Override
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
+ userId, displayId, mUsersOnSecondaryDisplays);
+ }
+ // TODO(b/240613396) throw exception if feature not supported
+
+ if (displayId == Display.INVALID_DISPLAY) {
+ synchronized (mUsersLock) {
+ if (mUsersOnSecondaryDisplays == null) {
+ if (false) {
+ // TODO(b/240613396): remove this if once we check for support above
+ Slogf.wtf(LOG_TAG, "assignUserToDisplay(%d, %d): no "
+ + "mUsersOnSecondaryDisplays", userId, displayId);
+ }
+ return;
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays", userId);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ if (mUsersOnSecondaryDisplays.size() == 0) {
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Removing mUsersOnSecondaryDisplays");
+ }
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+ return;
+ }
+
+ // TODO(b/239982558) check for invalid cases like:
+ // - userId already assigned to another display
+ // - 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);
+ }
+ mUsersOnSecondaryDisplays = new SparseIntArray(size);
+ }
+ if (DBG) {
+ Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
+ userId, displayId);
+ }
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
}
/**