diff options
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); + } + } } /** |