Changes AM.startUserInBgOnSecondaryDisplay() to check if display is valid.

Also adds APIs to list the valid displays:
- @TestApi AM getSecondaryDisplayIdsForStartingBackgroundUsers()
- cmd activity list-secondary-displays-for-starting-users
- system property
  (fw.secondary_display_for_starting_users_for_testing_purposes)
  to override it for testing purposes

Test: m -j update-api
Test: adb shell cmd activity list-secondary-displays-for-starting-users
Test: atest MultipleUsersOnMultipleDisplaysTest # CTS
Test: atest ActivityManagerServiceTest ActivityManagerServiceInjectorTest # unit

Bug: 239982558

Change-Id: Iaf5c83344075cce9985885a3f9359ebbd0174d41
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fb2c9e4..e08cef9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -121,6 +121,7 @@
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
     method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
     method public long getTotalRam();
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index da2a76a..576b572 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4387,13 +4387,15 @@
      * a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
      * does not exist.
      *
-     * @param displayId id of the display, it must exist.
+     * @param displayId id of the display.
      *
      * @return whether the operation succeeded. Notice that if the user was already started in such
      * display before, it will return {@code false}.
      *
      * @throws UnsupportedOperationException if the device does not support background users on
      * secondary displays.
+     * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to
+     * start secondary users on.
      *
      * @hide
      */
@@ -4414,6 +4416,24 @@
     }
 
     /**
+     * Gets the id of displays that can be used by
+     * {@link #startUserInBackgroundOnSecondaryDisplay(int, int)}.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
+    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        try {
+            return getService().getSecondaryDisplayIdsForStartingBackgroundUsers();
+        } 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 980b79b..b4abd3c 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -770,4 +770,10 @@
             "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
     boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
 
+    /**
+     * Gets the ids of displays that can be used on {@link #startUserInBackgroundOnSecondaryDisplay(int userId, int displayId)}.
+     *
+     * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+     */
+    @nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 50b47a6..7d85c13 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22,6 +22,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.Manifest.permission.MANAGE_USERS;
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
 import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
 import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
@@ -258,6 +259,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.media.audiofx.AudioEffect;
 import android.net.ConnectivityManager;
@@ -332,6 +334,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.util.proto.ProtoUtils;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -2366,6 +2369,7 @@
         mUiHandler = injector.getUiHandler(null /* service */);
         mUidObserverController = new UidObserverController(mUiHandler);
         mUserController = new UserController(this);
+        mInjector.mUserController = mUserController;
         mPendingIntentController =
                 new PendingIntentController(handlerThread.getLooper(), mUserController, mConstants);
         mAppRestrictionController = new AppRestrictionController(mContext, this);
@@ -2480,6 +2484,7 @@
         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
 
         mUserController = new UserController(this);
+        mInjector.mUserController = mUserController;
 
         mPendingIntentController = new PendingIntentController(
                 mHandlerThread.getLooper(), mUserController, mConstants);
@@ -6057,6 +6062,24 @@
      * This can be called with or without the global lock held.
      */
     @PermissionMethod
+    private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
+        for (String permission : permissions) {
+            if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+        }
+
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires one of " + Arrays.toString(permissions);
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
+    /**
+     * This can be called with or without the global lock held.
+     */
     void enforcePermission(String permission, int pid, int uid, String func) {
         if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
             return;
@@ -16333,8 +16356,34 @@
 
     @Override
     public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+        int[] displayIds = getSecondaryDisplayIdsForStartingBackgroundUsers();
+        boolean validDisplay = false;
+        if (displayIds != null) {
+            for (int i = 0; i < displayIds.length; i++) {
+                if (displayId == displayIds[i]) {
+                    validDisplay = true;
+                    break;
+                }
+            }
+        }
+        if (!validDisplay) {
+            throw new IllegalArgumentException("Invalid display (" + displayId + ") to start user. "
+                    + "Valid options are: " + Arrays.toString(displayIds));
+        }
+
+        if (DEBUG_MU) {
+            Slogf.d(TAG_MU, "Calling startUserOnSecondaryDisplay(%d, %d) using injector %s", userId,
+                    displayId, mInjector);
+        }
         // Permission check done inside UserController.
-        return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+        return mInjector.startUserOnSecondaryDisplay(userId, displayId);
+    }
+
+    @Override
+    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        enforceCallingHasAtLeastOnePermission("getSecondaryDisplayIdsForStartingBackgroundUsers()",
+                MANAGE_USERS, INTERACT_ACROSS_USERS);
+        return mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
     }
 
     /**
@@ -18343,8 +18392,10 @@
 
     @VisibleForTesting
     public static class Injector {
+        private final Context mContext;
         private NetworkManagementInternal mNmi;
-        private Context mContext;
+
+        private UserController mUserController;
 
         public Injector(Context context) {
             mContext = context;
@@ -18370,6 +18421,103 @@
         }
 
         /**
+         * Called by {@code AMS.getSecondaryDisplayIdsForStartingBackgroundUsers()}.
+         */
+        // NOTE: ideally Injector should have no complex logic, but if this logic was moved to AMS,
+        // it could not be tested with the existing ActivityManagerServiceTest (as DisplayManager,
+        // DisplayInfo, etc... are final and UserManager.isUsersOnSecondaryDisplaysEnabled is
+        // static).
+        // So, the logic was added here, and tested on ActivityManagerServiceInjectorTest (which
+        // was added on FrameworksMockingServicesTests and hence uses Extended Mockito to mock
+        // final and static stuff)
+        @Nullable
+        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+                Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): not supported");
+                return null;
+            }
+
+            // NOTE: DisplayManagerInternal doesn't have a method to list all displays
+            DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+            Display[] allDisplays = displayManager.getDisplays();
+
+            // allDisplays should contain at least Display.DEFAULT_DISPLAY, but it's better to
+            // double check, just in case...
+            if (allDisplays == null || allDisplays.length == 0) {
+                Slogf.wtf(TAG, "displayManager (%s) returned no displays", displayManager);
+                return null;
+            }
+            boolean hasDefaultDisplay = false;
+            for (Display display : allDisplays) {
+                if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    hasDefaultDisplay = true;
+                    break;
+                }
+            }
+            if (!hasDefaultDisplay) {
+                Slogf.wtf(TAG, "displayManager (%s) has %d displays (%s), but none has id "
+                        + "DEFAULT_DISPLAY (%d)", displayManager, allDisplays.length,
+                        Arrays.toString(allDisplays), Display.DEFAULT_DISPLAY);
+                return null;
+            }
+
+            // Starts with all displays but DEFAULT_DISPLAY
+            int[] displayIds = new int[allDisplays.length - 1];
+
+            // TODO(b/247592632): check for other properties like isSecure or proper display type
+            int numberValidDisplays = 0;
+            for (Display display : allDisplays) {
+                int displayId = display.getDisplayId();
+                if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+                    displayIds[numberValidDisplays++] = displayId;
+                }
+            }
+
+            if (numberValidDisplays == 0) {
+                // TODO(b/247580038): remove this workaround once a virtual display on Car's
+                // KitchenSink (or other app) can be used while running CTS tests on devices that
+                // don't have a real display.
+                // STOPSHIP: if not removed, it should at least be unit tested
+                String testingProp = "fw.secondary_display_for_starting_users_for_testing_purposes";
+                int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
+                if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
+                    Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+                            + "display found, but returning %d as set by property %s", displayId,
+                            testingProp);
+                    return new int[] { displayId };
+                }
+                Slogf.e(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid display"
+                        + " on %s", Arrays.toString(allDisplays));
+                return null;
+            }
+
+            if (numberValidDisplays != displayIds.length) {
+                int[] validDisplayIds = new int[numberValidDisplays];
+                System.arraycopy(displayIds, 0, validDisplayIds, 0, numberValidDisplays);
+                if (DEBUG_MU) {
+                    Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning "
+                            + "only valid displays (%d instead of %d): %s", numberValidDisplays,
+                            displayIds.length, Arrays.toString(validDisplayIds));
+                }
+                return validDisplayIds;
+            }
+
+            if (DEBUG_MU) {
+                Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning all "
+                        + "(but DEFAULT_DISPLAY) displays : %s", Arrays.toString(displayIds));
+            }
+            return displayIds;
+        }
+
+        /**
+         * Called by {@code AMS.startUserOnSecondaryDisplay()}.
+         */
+        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+            return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+        }
+
+        /**
          * Return the process list instance
          */
         public ProcessList getProcessList(ActivityManagerService service) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index b4f6e35..10e2aae 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -367,6 +367,8 @@
                     return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
                 case "reset-dropbox-rate-limiter":
                     return runResetDropboxRateLimiter();
+                case "list-secondary-displays-for-starting-users":
+                    return runListSecondaryDisplaysForStartingUsers(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -2068,6 +2070,10 @@
             success = mInterface.startUserInBackgroundWithListener(userId, waiter);
             displaySuffix = "";
         } else {
+            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+                pw.println("Not supported");
+                return -1;
+            }
             success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
             displaySuffix = " on display " + displayId;
         }
@@ -3591,6 +3597,14 @@
         return 0;
     }
 
+    int runListSecondaryDisplaysForStartingUsers(PrintWriter pw) throws RemoteException {
+        int[] displayIds = mInterface.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        pw.println(displayIds == null || displayIds.length == 0
+                ? "none"
+                : Arrays.toString(displayIds));
+        return 0;
+    }
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -3951,6 +3965,9 @@
             pw.println("         Set an app's background restriction level which in turn map to a app standby bucket.");
             pw.println("  get-bg-restriction-level [--user <USER_ID>] <PACKAGE>");
             pw.println("         Get an app's background restriction level.");
+            pw.println("  list-secondary-displays-for-starting-users");
+            pw.println("         Lists the id of displays that can be used to start users on "
+                    + "background.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
new file mode 100644
index 0000000..09df96f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.am.ActivityManagerService.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+/**
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
+ */
+public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+
+    private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
+
+    private final Display mDefaultDisplay = validDisplay(DEFAULT_DISPLAY);
+
+    @Mock private Context mContext;
+    @Mock private DisplayManager mDisplayManager;
+
+    private Injector mInjector;
+
+    @Before
+    public void setFixture() {
+        mInjector = new Injector(mContext);
+
+        when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+    }
+
+    @Override
+    protected void initializeSession(StaticMockitoSessionBuilder builder) {
+        builder.spyStatic(UserManager.class);
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_notSupported() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(false);
+
+        int [] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDisplaysAtAll() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays();
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_defaultDisplayOnly() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(mDefaultDisplay);
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDefaultDisplay() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(validDisplay(42));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNull();
+    }
+
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(mDefaultDisplay, validDisplay(42), invalidDisplay(108));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).isNotNull();
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(42);
+    }
+
+    // Extra test to make sure the array is properly copied...
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed_invalidFirst() {
+        mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+        mockGetDisplays(invalidDisplay(108), mDefaultDisplay, validDisplay(42));
+
+        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(42);
+    }
+
+    private Display validDisplay(int displayId) {
+        return mockDisplay(displayId, /* valid= */ true);
+    }
+
+    private Display invalidDisplay(int displayId) {
+        return mockDisplay(displayId, /* valid= */ false);
+    }
+
+    private Display mockDisplay(int displayId, boolean valid) {
+        Display display = mock(Display.class);
+
+        when(display.getDisplayId()).thenReturn(displayId);
+        when(display.isValid()).thenReturn(valid);
+
+        return display;
+    }
+
+    private void mockGetDisplays(Display... displays) {
+        Log.d(TAG, "mockGetDisplays(): " + Arrays.toString(displays));
+        when(mDisplayManager.getDisplays()).thenReturn(displays);
+    }
+
+    private void mockUmIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
+        Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
+        doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 3c17102..0d6f326 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -37,11 +37,14 @@
 import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
 import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -62,7 +65,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -74,6 +76,8 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.IntArray;
+import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
@@ -116,6 +120,7 @@
     private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
 
     private static final int TEST_UID = 11111;
+    private static final int USER_ID = 666;
 
     private static final long TEST_PROC_STATE_SEQ1 = 555;
     private static final long TEST_PROC_STATE_SEQ2 = 556;
@@ -147,8 +152,8 @@
     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
 
     private Context mContext = getInstrumentation().getTargetContext();
+
     @Mock private AppOpsService mAppOpsService;
-    @Mock private PackageManager mPackageManager;
 
     private TestInjector mInjector;
     private ActivityManagerService mAms;
@@ -828,6 +833,57 @@
                 true); // expectWait
     }
 
+    @Test
+    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+        int [] displayIds = mAms.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+                .that(displayIds).asList().containsExactly(4, 8, 15, 16, 23, 42);
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_invalidDisplay() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 666));
+
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays).isEmpty();
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_failed() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+        mInjector.returnValueForstartUserOnSecondaryDisplay = false;
+
+        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        Log.v(TAG, "Started: " + started);
+
+        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+                .that(started).isFalse();
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays)
+                .containsExactly(new Pair<>(USER_ID, 42));
+    }
+
+    @Test
+    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_success() {
+        mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+        mInjector.returnValueForstartUserOnSecondaryDisplay = true;
+
+        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        Log.v(TAG, "Started: " + started);
+
+        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+                .that(started).isTrue();
+        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+                .that(mInjector.usersStartedOnSecondaryDisplays)
+                .containsExactly(new Pair<>(USER_ID, 42));
+    }
+
     private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
             long lastNetworkUpdatedProcStateSeq,
             final long procStateSeqToWait, boolean expectWait) throws Exception {
@@ -922,7 +978,11 @@
     }
 
     private class TestInjector extends Injector {
-        private boolean mRestricted = true;
+        public boolean restricted = true;
+        public int[] secondaryDisplayIdsForStartingBackgroundUsers;
+
+        public boolean returnValueForstartUserOnSecondaryDisplay;
+        public List<Pair<Integer, Integer>> usersStartedOnSecondaryDisplays = new ArrayList<>();
 
         TestInjector(Context context) {
             super(context);
@@ -940,11 +1000,18 @@
 
         @Override
         public boolean isNetworkRestrictedForUid(int uid) {
-            return mRestricted;
+            return restricted;
         }
 
-        public void setNetworkRestrictedForUid(boolean restricted) {
-            mRestricted = restricted;
+        @Override
+        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+            return secondaryDisplayIdsForStartingBackgroundUsers;
+        }
+
+        @Override
+        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+            usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
+            return returnValueForstartUserOnSecondaryDisplay;
         }
     }
 }