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