Changes how `am start-user` starts a profile.
When the user is a profile, it will call IActivityManager's
.startProfileWithListener(), unless called with --force-invisible
(in which case it will call startUserInBackgroundWithListener())
Also improved logging on ActivityManagerShellCommand.
Test: adb shell am start-user --force-invisible 10
Test: atest android.server.wm.StartActivityAsUserTests#startActivityAsValidUserWithOptions
Fixes: 266094415
Change-Id: If2aff41fd165da77ea4de753da76a47a3d02da71
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 9dc8ce6..058c389 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -795,7 +795,7 @@
// Start (?) of T transactions
/**
- * Similar to {@link #startUserInBackgroundWithListener(int userId, IProgressListener unlockProgressListener),
+ * 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).
*
@@ -806,6 +806,12 @@
boolean startUserInBackgroundVisibleOnDisplay(int userid, int displayId);
/**
+ * Similar to {@link #startProfile(int userId)}, but with a listener to report user unlock
+ * progress.
+ */
+ boolean startProfileWithListener(int userid, IProgressListener unlockProgressListener);
+
+ /**
* Gets the ids of displays that can be used on {@link #startUserInBackgroundVisibleOnDisplay(int userId, int displayId)}.
*
* <p>Typically used only by automotive builds when the vehicle has multiple displays.
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 10d50c2..7ca9b7d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16727,6 +16727,12 @@
}
@Override
+ public boolean startProfileWithListener(@UserIdInt int userId,
+ @Nullable IProgressListener unlockListener) {
+ return mUserController.startProfile(userId, unlockListener);
+ }
+
+ @Override
public boolean stopProfile(@UserIdInt int userId) {
return mUserController.stopProfile(userId);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 1e97285..3ab1cd7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -39,6 +39,7 @@
import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_COUNT;
import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -113,10 +114,12 @@
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.util.MemInfoReader;
+import com.android.server.LocalServices;
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.nano.Capabilities;
import com.android.server.am.nano.Capability;
import com.android.server.compat.PlatformCompat;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
import dalvik.annotation.optimization.NeverCompile;
@@ -536,18 +539,32 @@
private class ProgressWaiter extends IProgressListener.Stub {
private final CountDownLatch mFinishedLatch = new CountDownLatch(1);
+ private final @UserIdInt int mUserId;
+
+ private ProgressWaiter(@UserIdInt int userId) {
+ mUserId = userId;
+ }
@Override
public void onStarted(int id, Bundle extras) {}
@Override
- public void onProgress(int id, int progress, Bundle extras) {}
+ public void onProgress(int id, int progress, Bundle extras) {
+ Slogf.d(TAG, "ProgressWaiter[user=%d]: onProgress(%d, %d)", mUserId, id, progress);
+ }
@Override
public void onFinished(int id, Bundle extras) {
+ Slogf.d(TAG, "ProgressWaiter[user=%d]: onFinished(%d)", mUserId, id);
mFinishedLatch.countDown();
}
+ @Override
+ public String toString() {
+ return "ProgressWaiter[userId=" + mUserId + ", finished="
+ + (mFinishedLatch.getCount() == 0) + "]";
+ }
+
public boolean waitForFinish(long timeoutMillis) {
try {
return mFinishedLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
@@ -2183,6 +2200,7 @@
boolean wait = false;
String opt;
int displayId = Display.INVALID_DISPLAY;
+ boolean forceInvisible = false;
while ((opt = getNextOption()) != null) {
switch(opt) {
case "-w":
@@ -2191,32 +2209,47 @@
case "--display":
displayId = getDisplayIdFromNextArg();
break;
+ case "--force-invisible":
+ forceInvisible = true;
+ break;
default:
getErrPrintWriter().println("Error: unknown option: " + opt);
return -1;
}
}
- int userId = Integer.parseInt(getNextArgRequired());
-
- final ProgressWaiter waiter = wait ? new ProgressWaiter() : null;
+ final int userId = Integer.parseInt(getNextArgRequired());
+ final boolean callStartProfile = !forceInvisible && isProfile(userId);
+ final ProgressWaiter waiter = wait ? new ProgressWaiter(userId) : null;
+ Slogf.d(TAG, "runStartUser(): userId=%d, display=%d, waiter=%s, callStartProfile=%b, "
+ + "forceInvisible=%b", userId, displayId, waiter, callStartProfile,
+ forceInvisible);
boolean success;
- String displaySuffix;
+ String displaySuffix = "";
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runStartUser" + userId);
try {
- if (displayId == Display.INVALID_DISPLAY) {
+ if (callStartProfile) {
+ Slogf.d(TAG, "calling startProfileWithListener(%d, %s)", userId, waiter);
+ // startProfileWithListener() will start the profile visible (as long its parent is
+ // the current user), while startUserInBackgroundWithListener() will always start
+ // the user (or profile) invisible
+ success = mInterface.startProfileWithListener(userId, waiter);
+ } else if (displayId == Display.INVALID_DISPLAY) {
+ Slogf.d(TAG, "calling startUserInBackgroundWithListener(%d)", userId);
success = mInterface.startUserInBackgroundWithListener(userId, waiter);
- displaySuffix = "";
} else {
if (!UserManager.isVisibleBackgroundUsersEnabled()) {
pw.println("Not supported");
return -1;
}
+ Slogf.d(TAG, "calling startUserInBackgroundVisibleOnDisplay(%d,%d)", userId,
+ displayId);
success = mInterface.startUserInBackgroundVisibleOnDisplay(userId, displayId);
displaySuffix = " on display " + displayId;
}
if (wait && success) {
+ Slogf.d(TAG, "waiting %d ms", USER_OPERATION_TIMEOUT_MS);
success = waiter.waitForFinish(USER_OPERATION_TIMEOUT_MS);
}
} finally {
@@ -2273,27 +2306,40 @@
}
static final class StopUserCallback extends IStopUserCallback.Stub {
+ private final @UserIdInt int mUserId;
private boolean mFinished = false;
+ private StopUserCallback(@UserIdInt int userId) {
+ mUserId = userId;
+ }
+
public synchronized void waitForFinish() {
try {
while (!mFinished) wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
+ Slogf.d(TAG, "user %d finished stopping", mUserId);
}
@Override
public synchronized void userStopped(int userId) {
+ Slogf.d(TAG, "StopUserCallback: userStopped(%d)", userId);
mFinished = true;
notifyAll();
}
@Override
public synchronized void userStopAborted(int userId) {
+ Slogf.d(TAG, "StopUserCallback: userStopAborted(%d)", userId);
mFinished = true;
notifyAll();
}
+
+ @Override
+ public String toString() {
+ return "ProgressWaiter[userId=" + mUserId + ", finished=" + mFinished + "]";
+ }
}
int runStopUser(PrintWriter pw) throws RemoteException {
@@ -2310,10 +2356,11 @@
return -1;
}
}
- int user = Integer.parseInt(getNextArgRequired());
- StopUserCallback callback = wait ? new StopUserCallback() : null;
+ int userId = Integer.parseInt(getNextArgRequired());
+ StopUserCallback callback = wait ? new StopUserCallback(userId) : null;
- int res = mInterface.stopUser(user, force, callback);
+ Slogf.d(TAG, "Calling stopUser(%d, %b, %s)", userId, force, callback);
+ int res = mInterface.stopUser(userId, force, callback);
if (res != ActivityManager.USER_OP_SUCCESS) {
String txt = "";
switch (res) {
@@ -2321,13 +2368,13 @@
txt = " (Can't stop current user)";
break;
case ActivityManager.USER_OP_UNKNOWN_USER:
- txt = " (Unknown user " + user + ")";
+ txt = " (Unknown user " + userId + ")";
break;
case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
txt = " (System user cannot be stopped)";
break;
case ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP:
- txt = " (Can't stop user " + user
+ txt = " (Can't stop user " + userId
+ " - one of its related users can't be stopped)";
break;
}
@@ -3822,6 +3869,11 @@
return new Resources(AssetManager.getSystem(), metrics, config);
}
+ private boolean isProfile(@UserIdInt int userId) {
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ return umi.getProfileParentId(userId) != userId;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -4052,13 +4104,18 @@
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] [--display DISPLAY_ID] <USER_ID>");
+ pw.println(" start-user [-w] [--display DISPLAY_ID] [--force-invisible] <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(" --display <DISPLAY_ID>: starts the user visible in that display, "
+ + "which allows the user to launch activities on it.");
+ pw.println(" (not supported on all devices; typically only on automotive builds "
+ + "where the vehicle has passenger displays)");
+ pw.println(" --force-invisible: always start the user invisible, even if it's a "
+ + "profile.");
+ pw.println(" (by default, a profile is visible in the default display when its "
+ + "parent is the current foreground user)");
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 8ce9889..b2e4740 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1470,7 +1470,11 @@
* @param userId the id of the user to start.
* @return true if the operation was successful.
*/
- boolean startProfile(final @UserIdInt int userId) {
+ boolean startProfile(@UserIdInt int userId) {
+ return startProfile(userId, /* unlockListener= */ null);
+ }
+
+ boolean startProfile(@UserIdInt int userId, @Nullable IProgressListener unlockListener) {
if (mInjector.checkCallingPermission(android.Manifest.permission.MANAGE_USERS)
== PackageManager.PERMISSION_DENIED && mInjector.checkCallingPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
@@ -1491,7 +1495,7 @@
}
return startUserNoChecks(userId, Display.DEFAULT_DISPLAY,
- USER_START_MODE_BACKGROUND_VISIBLE, /* unlockListener= */ null);
+ USER_START_MODE_BACKGROUND_VISIBLE, unlockListener);
}
@VisibleForTesting