Merge "Add provider facing CREATE APIs for Credential Manager."
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index be0e025..6af24be 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -227,6 +227,9 @@
     public static final String KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP =
             "js_min_satiated_balance_other_app";
     /** @hide */
+    public static final String KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER =
+            "js_min_satiated_balance_increment_updater";
+    /** @hide */
     public static final String KEY_JS_MAX_SATIATED_BALANCE =
             "js_max_satiated_balance";
     /** @hide */
@@ -509,6 +512,15 @@
     public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0);
     /** @hide */
     public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000);
+    /**
+     * How many credits to increase the updating app's min satiated balance by for each app that it
+     * is responsible for updating.
+     * @hide
+     */
+    public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES =
+            // Research indicates that the median time between popular app updates is 13-14 days,
+            // so adjust by 14 to amortize over that time.
+            DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES / 14;
     /** @hide */
     public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3);
     /** @hide */
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index c13e1dd9..4a26d21 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -81,6 +81,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
@@ -164,6 +165,10 @@
     @GuardedBy("mLock")
     private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
 
+    /** Set of apps each installer is responsible for installing. */
+    @GuardedBy("mLock")
+    private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
+
     private volatile boolean mHasBattery = true;
     private volatile boolean mIsEnabled;
     private volatile int mBootPhase;
@@ -353,6 +358,14 @@
         return mCompleteEconomicPolicy;
     }
 
+    /** Returns the number of apps that this app is expected to update at some point. */
+    int getAppUpdateResponsibilityCount(final int userId, @NonNull final String pkgName) {
+        synchronized (mLock) {
+            // TODO(248274798): return 0 if the app has lost the install permission
+            return ArrayUtils.size(mInstallers.get(userId, pkgName));
+        }
+    }
+
     @NonNull
     SparseArrayMap<String, InstalledPackageInfo> getInstalledPackages() {
         synchronized (mLock) {
@@ -525,7 +538,8 @@
         }
         synchronized (mLock) {
             final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
-            mPkgCache.add(userId, pkgName, ipo);
+            final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
+            maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             mUidToPackageCache.add(uid, pkgName);
             // TODO: only do this when the user first launches the app (app leaves stopped state)
             mAgent.grantBirthrightLocked(userId, pkgName);
@@ -552,7 +566,14 @@
         synchronized (mLock) {
             mUidToPackageCache.remove(uid, pkgName);
             mVipOverrides.delete(userId, pkgName);
-            mPkgCache.delete(userId, pkgName);
+            final InstalledPackageInfo ipo = mPkgCache.delete(userId, pkgName);
+            mInstallers.delete(userId, pkgName);
+            if (ipo != null && ipo.installerPackageName != null) {
+                final ArraySet<String> list = mInstallers.get(userId, ipo.installerPackageName);
+                if (list != null) {
+                    list.remove(pkgName);
+                }
+            }
             mAgent.onPackageRemovedLocked(userId, pkgName);
         }
     }
@@ -574,7 +595,8 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
-                mPkgCache.add(userId, ipo.packageName, ipo);
+                final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+                maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
             mAgent.grantBirthrightsLocked(userId);
         }
@@ -590,6 +612,7 @@
                     mUidToPackageCache.remove(pkgInfo.uid);
                 }
             }
+            mInstallers.delete(userId);
             mPkgCache.delete(userId);
             mAgent.onUserRemovedLocked(userId);
         }
@@ -746,11 +769,49 @@
                     mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
             for (int i = pkgs.size() - 1; i >= 0; --i) {
                 final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
-                mPkgCache.add(userId, ipo.packageName, ipo);
+                final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+                maybeUpdateInstallerStatusLocked(oldIpo, ipo);
             }
         }
     }
 
+    /**
+     * Used to update the set of installed apps for each installer. This only has an effect if the
+     * installer package name is different between {@code oldIpo} and {@code newIpo}.
+     */
+    @GuardedBy("mLock")
+    private void maybeUpdateInstallerStatusLocked(@Nullable InstalledPackageInfo oldIpo,
+            @NonNull InstalledPackageInfo newIpo) {
+        final boolean changed;
+        if (oldIpo == null) {
+            changed = newIpo.installerPackageName != null;
+        } else {
+            changed = !Objects.equals(oldIpo.installerPackageName, newIpo.installerPackageName);
+        }
+        if (!changed) {
+            return;
+        }
+        // InstallSourceInfo doesn't track userId, so for now, assume the installer on the package's
+        // user profile did the installation.
+        // TODO(246640162): use the actual installer's user ID
+        final int userId = UserHandle.getUserId(newIpo.uid);
+        final String pkgName = newIpo.packageName;
+        if (oldIpo != null) {
+            final ArraySet<String> oldList = mInstallers.get(userId, oldIpo.installerPackageName);
+            if (oldList != null) {
+                oldList.remove(pkgName);
+            }
+        }
+        if (newIpo.installerPackageName != null) {
+            ArraySet<String> newList = mInstallers.get(userId, newIpo.installerPackageName);
+            if (newList == null) {
+                newList = new ArraySet<>();
+                mInstallers.add(userId, newIpo.installerPackageName, newList);
+            }
+            newList.add(pkgName);
+        }
+    }
+
     private void registerListeners() {
         final IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
@@ -1360,6 +1421,23 @@
             pw.println();
 
             pw.println();
+            pw.println("Installers:");
+            pw.increaseIndent();
+            for (int u = 0; u < mInstallers.numMaps(); ++u) {
+                final int userId = mInstallers.keyAt(u);
+
+                for (int p = 0; p < mInstallers.numElementsForKeyAt(u); ++p) {
+                    final String pkgName = mInstallers.keyAt(u, p);
+
+                    pw.print(appToString(userId, pkgName));
+                    pw.print(": ");
+                    pw.print(mInstallers.valueAt(u, p).size());
+                    pw.println(" apps");
+                }
+            }
+            pw.decreaseIndent();
+
+            pw.println();
             mCompleteEconomicPolicy.dump(pw);
 
             pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 55cc352..322e824 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -42,6 +42,7 @@
 import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES;
@@ -87,6 +88,7 @@
 import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
 import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT;
 import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_MAX;
@@ -154,6 +156,7 @@
 
     private long mMinSatiatedBalanceExempted;
     private long mMinSatiatedBalanceOther;
+    private long mMinSatiatedBalanceIncrementalAppUpdater;
     private long mMaxSatiatedBalance;
     private long mInitialSatiatedConsumptionLimit;
     private long mHardSatiatedConsumptionLimit;
@@ -183,11 +186,20 @@
         if (mIrs.isPackageRestricted(userId, pkgName)) {
             return 0;
         }
+
+        final long baseBalance;
         if (mIrs.isPackageExempted(userId, pkgName)) {
-            return mMinSatiatedBalanceExempted;
+            baseBalance = mMinSatiatedBalanceExempted;
+        } else {
+            baseBalance = mMinSatiatedBalanceOther;
         }
-        // TODO: take other exemptions into account
-        return mMinSatiatedBalanceOther;
+
+        long minBalance = baseBalance;
+
+        final int updateResponsibilityCount = mIrs.getAppUpdateResponsibilityCount(userId, pkgName);
+        minBalance += updateResponsibilityCount * mMinSatiatedBalanceIncrementalAppUpdater;
+
+        return Math.min(minBalance, mMaxSatiatedBalance);
     }
 
     @Override
@@ -242,6 +254,9 @@
         mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
             KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
             mMinSatiatedBalanceOther);
+        mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
+                KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
         mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
             KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
@@ -397,10 +412,11 @@
 
     @Override
     void dump(IndentingPrintWriter pw) {
-        pw.println("Min satiated balances:");
+        pw.println("Min satiated balance:");
         pw.increaseIndent();
         pw.print("Exempted", cakeToString(mMinSatiatedBalanceExempted)).println();
         pw.print("Other", cakeToString(mMinSatiatedBalanceOther)).println();
+        pw.print("+App Updater", cakeToString(mMinSatiatedBalanceIncrementalAppUpdater)).println();
         pw.decreaseIndent();
         pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
         pw.print("Consumption limits: [");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index 620d1a0d..a68170c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -286,7 +286,7 @@
             final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS)
                     % NUM_REWARD_BUCKET_WINDOWS;
             final RewardBucket rewardBucket = mRewardBuckets[idx];
-            if (rewardBucket == null) {
+            if (rewardBucket == null || rewardBucket.startTimeMs == 0) {
                 continue;
             }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 65c1f46..c7ca0cd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4426,6 +4426,7 @@
     method public void readFromParcel(android.os.Parcel);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ActivityManager.MemoryInfo> CREATOR;
+    field public long advertisedMem;
     field public long availMem;
     field public boolean lowMemory;
     field public long threshold;
@@ -18645,8 +18646,10 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
     field public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1; // 0x1
-    field public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
+    field public static final int LIGHT_CAPABILITY_COLOR_RGB = 2; // 0x2
+    field @Deprecated public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
     field public static final int LIGHT_TYPE_INPUT = 10001; // 0x2711
+    field public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003; // 0x2713
     field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
     field public static final int LIGHT_TYPE_PLAYER_ID = 10002; // 0x2712
   }
@@ -51882,10 +51885,11 @@
     method public void setSpeechStateChangeTypes(int);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+    field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
     field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
     field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
     field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
-    field public static final int CONTENT_CHANGE_TYPE_INVALID = 1024; // 0x400
+    field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
     field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
     field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
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 8ae16df6..576b572 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -29,6 +29,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -2812,6 +2813,15 @@
      */
     public static class MemoryInfo implements Parcelable {
         /**
+         * The advertised memory of the system, as the end user would encounter in a retail display
+         * environment. This value might be different from {@code totalMem}. This could be due to
+         * many reasons. For example, the ODM could reserve part of the memory for the Trusted
+         * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it.
+         */
+        @SuppressLint("MutableBareField")
+        public long advertisedMem;
+
+        /**
          * The available memory on the system.  This number should not
          * be considered absolute: due to the nature of the kernel, a significant
          * portion of this memory is actually in use and needed for the overall
@@ -2860,6 +2870,7 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(advertisedMem);
             dest.writeLong(availMem);
             dest.writeLong(totalMem);
             dest.writeLong(threshold);
@@ -2871,6 +2882,7 @@
         }
 
         public void readFromParcel(Parcel source) {
+            advertisedMem = source.readLong();
             availMem = source.readLong();
             totalMem = source.readLong();
             threshold = source.readLong();
@@ -4375,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
      */
@@ -4402,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/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d4c9a42..fadfa5c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -445,8 +445,8 @@
                 @Nullable Executor executor,
                 @Nullable AudioConfigurationChangeCallback callback) {
             if (mVirtualAudioDevice == null) {
-                mVirtualAudioDevice = new VirtualAudioDevice(
-                        mContext, mVirtualDevice, display, executor, callback);
+                mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+                        executor, callback, () -> mVirtualAudioDevice = null);
             }
             return mVirtualAudioDevice;
         }
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
index 0db7b5f..e200a11 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -64,11 +64,24 @@
         void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
     }
 
+    /**
+     * Interface to be notified when {@link #close()} is called.
+     *
+     * @hide
+     */
+    public interface CloseListener {
+        /**
+         * Notifies when {@link #close()} is called.
+         */
+        void onClosed();
+    }
+
     private final Context mContext;
     private final IVirtualDevice mVirtualDevice;
     private final VirtualDisplay mVirtualDisplay;
     private final AudioConfigurationChangeCallback mCallback;
     private final Executor mExecutor;
+    private final CloseListener mListener;
     @Nullable
     private VirtualAudioSession mOngoingSession;
 
@@ -77,12 +90,13 @@
      */
     public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
             @NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
-            @Nullable AudioConfigurationChangeCallback callback) {
+            @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener) {
         mContext = context;
         mVirtualDevice = virtualDevice;
         mVirtualDisplay = virtualDisplay;
         mExecutor = executor;
         mCallback = callback;
+        mListener = listener;
     }
 
     /**
@@ -169,6 +183,10 @@
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
+
+            if (mListener != null) {
+                mListener.onClosed();
+            }
         }
     }
 }
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index c311379..1df9b75 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -60,15 +60,29 @@
     public static final int LIGHT_TYPE_PLAYER_ID = 10002;
 
     /**
+     * Type for lights that illuminate keyboard keys.
+     */
+    public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003;
+
+    /**
      * Capability for lights that could adjust its LED brightness. If the capability is not present
-     * the led can only be turned either on or off.
+     * the LED can only be turned either on or off.
      */
     public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1 << 0;
 
     /**
-     * Capability for lights that has red, green and blue LEDs to control the light's color.
+     * Capability for lights that have red, green and blue LEDs to control the light's color.
      */
-    public static final int LIGHT_CAPABILITY_RGB = 0 << 1;
+    public static final int LIGHT_CAPABILITY_COLOR_RGB = 1 << 1;
+
+    /**
+     * Capability for lights that have red, green and blue LEDs to control the light's color.
+     *
+     * @deprecated Wrong int based flag with value 0. Use capability flag {@code
+     * LIGHT_CAPABILITY_COLOR_RGB} instead.
+     */
+    @Deprecated
+    public static final int LIGHT_CAPABILITY_RGB = 0;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -77,6 +91,7 @@
             LIGHT_TYPE_MICROPHONE,
             LIGHT_TYPE_INPUT,
             LIGHT_TYPE_PLAYER_ID,
+            LIGHT_TYPE_KEYBOARD_BACKLIGHT,
         })
     public @interface LightType {}
 
@@ -85,6 +100,7 @@
     @IntDef(flag = true, prefix = {"LIGHT_CAPABILITY_"},
         value = {
             LIGHT_CAPABILITY_BRIGHTNESS,
+            LIGHT_CAPABILITY_COLOR_RGB,
             LIGHT_CAPABILITY_RGB,
         })
     public @interface LightCapability {}
@@ -233,7 +249,7 @@
      * @return True if the hardware can control the RGB led, otherwise false.
      */
     public boolean hasRgbControl() {
-        return (mCapabilities & LIGHT_CAPABILITY_RGB) == LIGHT_CAPABILITY_RGB;
+        return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB;
     }
 
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index edfcb3d..d5c3de1 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -54,6 +54,7 @@
 import android.system.Os;
 import android.system.StructStat;
 import android.text.TextUtils;
+import android.util.DataUnit;
 import android.util.Log;
 import android.util.Slog;
 import android.webkit.MimeTypeMap;
@@ -83,6 +84,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
@@ -1309,6 +1311,85 @@
         return val * pow;
     }
 
+    private static long toBytes(long value, String unit) {
+        unit = unit.toUpperCase();
+
+        if (List.of("B").contains(unit)) {
+            return value;
+        }
+
+        if (List.of("K", "KB").contains(unit)) {
+            return DataUnit.KILOBYTES.toBytes(value);
+        }
+
+        if (List.of("M", "MB").contains(unit)) {
+            return DataUnit.MEGABYTES.toBytes(value);
+        }
+
+        if (List.of("G", "GB").contains(unit)) {
+            return DataUnit.GIGABYTES.toBytes(value);
+        }
+
+        if (List.of("KI", "KIB").contains(unit)) {
+            return DataUnit.KIBIBYTES.toBytes(value);
+        }
+
+        if (List.of("MI", "MIB").contains(unit)) {
+            return DataUnit.MEBIBYTES.toBytes(value);
+        }
+
+        if (List.of("GI", "GIB").contains(unit)) {
+            return DataUnit.GIBIBYTES.toBytes(value);
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    /**
+     * @param fmtSize The string that contains the size to be parsed. The
+     *   expected format is:
+     *
+     *   <p>"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$"
+     *
+     *   <p>For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive.
+     *
+     * @return the size in bytes. If {@code fmtSize} has invalid format, it
+     *   returns {@link Long#MIN_VALUE}.
+     * @hide
+     */
+    public static long parseSize(@Nullable String fmtSize) {
+        if (fmtSize == null || fmtSize.isBlank()) {
+            return Long.MIN_VALUE;
+        }
+
+        int sign = 1;
+        fmtSize = fmtSize.trim();
+        char first = fmtSize.charAt(0);
+        if (first == '-' ||  first == '+') {
+            if (first == '-') {
+                sign = -1;
+            }
+
+            fmtSize = fmtSize.replace(first + "", "");
+        }
+
+        int index = 0;
+        // Find the last index of the value in fmtSize.
+        while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) {
+            index++;
+        }
+
+        // Check if number and units are present.
+        if (index == 0 || index == fmtSize.length()) {
+            return Long.MIN_VALUE;
+        }
+
+        long value = sign * Long.valueOf(fmtSize.substring(0, index));
+        String unit = fmtSize.substring(index).trim();
+
+        return toBytes(value, unit);
+    }
+
     /**
      * Closes the given object quietly, ignoring any checked exceptions. Does
      * nothing if the given object is {@code null}.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3..c943a3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -26,6 +26,7 @@
 import android.annotation.UptimeMillisLong;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build.VERSION_CODES;
+import android.sysprop.MemoryProperties;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -1330,6 +1331,24 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public static final native void sendSignalQuiet(int pid, int signal);
 
+    /**
+     * @return The advertised memory of the system, as the end user would encounter in a retail
+     * display environment. If the advertised memory is not defined, it returns
+     * {@code getTotalMemory()} rounded.
+     *
+     * @hide
+     */
+    public static final long getAdvertisedMem() {
+        String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
+        long memSize = FileUtils.parseSize(formatSize);
+
+        if (memSize == Long.MIN_VALUE) {
+            return FileUtils.roundStorageSize(getTotalMemory());
+        }
+
+        return memSize;
+    }
+
     /** @hide */
     @UnsupportedAppUsage
     public static final native long getFreeMemory();
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1b46107..1285d1e 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,6 +345,12 @@
      * Calling this a second time invalidates the previously created hotword detector
      * which can no longer be used to manage recognition.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+     * AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
+     * SharedMemory, HotwordDetector.Callback)}, call this will throw an
+     * {@link IllegalArgumentException}.
+     *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      * @param callback The callback to notify of detection events.
@@ -377,6 +383,10 @@
      * <p>Note: The system will trigger hotword detection service after calling this function when
      * all conditions meet the requirements.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+     * call this will throw an {@link IllegalArgumentException}.
+     *
      * @param keyphrase The keyphrase that's being used, for example "Hello Android".
      * @param locale The locale for which the enrollment needs to be performed.
      * @param options Application configuration data provided by the
@@ -420,6 +430,14 @@
                 safelyShutdownAllHotwordDetectors();
             }
 
+            for (HotwordDetector detector : mActiveHotwordDetectors) {
+                if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
+                    throw new IllegalArgumentException(
+                            "It disallows to create trusted and non-trusted detectors "
+                                    + "at the same time.");
+                }
+            }
+
             AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
                     callback, mKeyphraseEnrollmentInfo, mSystemService,
                     getApplicationContext().getApplicationInfo().targetSdkVersion,
@@ -460,6 +478,10 @@
      * devices where hardware filtering is available (such as through a DSP), it's highly
      * recommended to use {@link #createAlwaysOnHotwordDetector} instead.
      *
+     * <p>Note: If there are any active detectors that are created by using
+     * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+     * call this will throw an {@link IllegalArgumentException}.
+     *
      * @param options Application configuration data to be provided to the
      * {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
      * other contents that can be used to communicate with other processes.
@@ -490,7 +512,11 @@
                 safelyShutdownAllHotwordDetectors();
             } else {
                 for (HotwordDetector detector : mActiveHotwordDetectors) {
-                    if (detector instanceof SoftwareHotwordDetector) {
+                    if (!detector.isUsingHotwordDetectionService()) {
+                        throw new IllegalArgumentException(
+                                "It disallows to create trusted and non-trusted detectors "
+                                        + "at the same time.");
+                    } else if (detector instanceof SoftwareHotwordDetector) {
                         throw new IllegalArgumentException(
                                 "There is already an active SoftwareHotwordDetector. "
                                         + "It must be destroyed to create a new one.");
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f2c8355..f86f51fc 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.TextView;
 
 import com.android.internal.util.BitUtils;
 
@@ -688,15 +687,27 @@
 
     /**
      * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
-     * It means the content is invalid or associated with an error.
-     * For example, text that sets an error message, such as when input isn't in a valid format,
-     * should send this event and use {@link AccessibilityNodeInfo#setError} to
-     * provide more context.
+     * The source node changed its content validity returned by
+     * {@link AccessibilityNodeInfo#isContentInvalid}.
+     * The view changing content validity should call
+     * {@link AccessibilityNodeInfo#setContentInvalid} and then send this event.
      *
-     * @see AccessibilityNodeInfo#setError
-     * @see TextView#setError
+     * @see AccessibilityNodeInfo#isContentInvalid
+     * @see AccessibilityNodeInfo#setContentInvalid
      */
-    public static final int CONTENT_CHANGE_TYPE_INVALID = 0x0000400;
+    public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 0x0000400;
+
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The source node changed its erroneous content's error message returned by
+     * {@link AccessibilityNodeInfo#getError}.
+     * The view changing erroneous content's error message should call
+     * {@link AccessibilityNodeInfo#setError} and then send this event.
+     *
+     * @see AccessibilityNodeInfo#getError
+     * @see AccessibilityNodeInfo#setError
+     */
+    public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
 
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
     public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
@@ -823,7 +834,8 @@
                 CONTENT_CHANGE_TYPE_DRAG_STARTED,
                 CONTENT_CHANGE_TYPE_DRAG_DROPPED,
                 CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
-                CONTENT_CHANGE_TYPE_INVALID,
+                CONTENT_CHANGE_TYPE_CONTENT_INVALID,
+                CONTENT_CHANGE_TYPE_ERROR,
             })
     public @interface ContentChangeTypes {}
 
@@ -1090,7 +1102,9 @@
             case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
             case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
             case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
-            case CONTENT_CHANGE_TYPE_INVALID: return "CONTENT_CHANGE_TYPE_INVALID";
+            case CONTENT_CHANGE_TYPE_CONTENT_INVALID:
+                return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
+            case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
             default: return Integer.toHexString(type);
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 752d02c..d11fa5f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7650,7 +7650,8 @@
         createEditorIfNeeded();
         mEditor.setError(error, icon);
         notifyViewAccessibilityStateChangedIfNeeded(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_INVALID);
+                AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
+                        | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
     }
 
     @Override
@@ -12221,6 +12222,11 @@
         }
     }
 
+    @Override
+    public boolean isAutoHandwritingEnabled() {
+        return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
+    }
+
     /** @hide */
     @Override
     public boolean isStylusHandwritingAvailable() {
diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
new file mode 100644
index 0000000..6139bce
--- /dev/null
+++ b/core/java/com/android/internal/jank/EventLogTags.logtags
@@ -0,0 +1,10 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.internal.jank;
+
+# Marks a request to start tracing a CUJ. Doesn't mean the request was executed.
+37001 jank_cuj_events_begin_request (CUJ Type|1|5)
+# Marks a request to end tracing a CUJ. Doesn't mean the request was executed.
+37002 jank_cuj_events_end_request (CUJ Type|1|5)
+# Marks a request to cancel tracing a CUJ. Doesn't mean the request was executed.
+37003 jank_cuj_events_cancel_request (CUJ Type|1|5)
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 40d192e..76f33a6 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -541,6 +541,7 @@
     public boolean begin(@NonNull Configuration.Builder builder) {
         try {
             final Configuration config = builder.build();
+            EventLogTags.writeJankCujEventsBeginRequest(config.mCujType);
             final TrackerResult result = new TrackerResult();
             final boolean success = config.getHandler().runWithScissors(
                     () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
@@ -614,6 +615,7 @@
      * @return boolean true if the tracker is ended successfully, false otherwise.
      */
     public boolean end(@CujType int cujType) {
+        EventLogTags.writeJankCujEventsEndRequest(cujType);
         FrameTracker tracker = getTracker(cujType);
         // Skip this call since we haven't started a trace yet.
         if (tracker == null) return false;
@@ -651,6 +653,7 @@
      * @return boolean true if the tracker is cancelled successfully, false otherwise.
      */
     public boolean cancel(@CujType int cujType) {
+        EventLogTags.writeJankCujEventsCancelRequest(cujType);
         return cancel(cujType, REASON_CANCEL_NORMAL);
     }
 
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
index 3ecc7ff..bf65af3 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -131,7 +131,7 @@
             new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
                         Light.LIGHT_CAPABILITY_BRIGHTNESS),
             new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
                         0 /* capabilities */)
         };
@@ -150,13 +150,13 @@
 
         Light[] mockedLights = {
             new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB),
+                        Light.LIGHT_CAPABILITY_COLOR_RGB),
             new Light(4 /* id */, "Light4", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                        Light.LIGHT_CAPABILITY_RGB)
+                        Light.LIGHT_CAPABILITY_COLOR_RGB)
         };
         mockLights(mockedLights);
 
@@ -204,7 +204,7 @@
                 new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
                             0 /* capabilities */),
                 new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                            Light.LIGHT_CAPABILITY_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
+                            Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
                 new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
                             Light.LIGHT_CAPABILITY_BRIGHTNESS)
         };
@@ -239,9 +239,9 @@
     @Test
     public void testLightCapabilities() throws Exception {
         Light light = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
-                Light.LIGHT_CAPABILITY_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
+                Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
         assertThat(light.getType()).isEqualTo(Light.LIGHT_TYPE_INPUT);
-        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_RGB
+        assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_COLOR_RGB
                 | Light.LIGHT_CAPABILITY_BRIGHTNESS);
         assertTrue(light.hasBrightnessControl());
         assertTrue(light.hasRgbControl());
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 32c3a26..91fbe00 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -532,6 +532,56 @@
     }
 
     @Test
+    public void testParseSize() {
+        assertEquals(0L, FileUtils.parseSize("0MB"));
+        assertEquals(1_024L, FileUtils.parseSize("1024b"));
+        assertEquals(-1L, FileUtils.parseSize(" -1 b "));
+        assertEquals(0L, FileUtils.parseSize(" -0 gib "));
+        assertEquals(1_000L, FileUtils.parseSize("1K"));
+        assertEquals(1_000L, FileUtils.parseSize("1KB"));
+        assertEquals(10_000L, FileUtils.parseSize("10KB"));
+        assertEquals(100_000L, FileUtils.parseSize("100KB"));
+        assertEquals(1_000_000L, FileUtils.parseSize("1000KB"));
+        assertEquals(1_024_000L, FileUtils.parseSize("1000KiB"));
+        assertEquals(70_000_000L, FileUtils.parseSize("070M"));
+        assertEquals(70_000_000L, FileUtils.parseSize("070MB"));
+        assertEquals(73_400_320L, FileUtils.parseSize("70MiB"));
+        assertEquals(700_000_000L, FileUtils.parseSize("700000KB"));
+        assertEquals(200_000_000L, FileUtils.parseSize("+200MB"));
+        assertEquals(1_000_000_000L, FileUtils.parseSize("1000MB"));
+        assertEquals(1_000_000_000L, FileUtils.parseSize("+1000 mb"));
+        assertEquals(644_245_094_400L, FileUtils.parseSize("600GiB"));
+        assertEquals(999_000_000_000L, FileUtils.parseSize("999GB"));
+        assertEquals(999_000_000_000L, FileUtils.parseSize("999 gB"));
+        assertEquals(9_999_000_000_000L, FileUtils.parseSize("9999GB"));
+        assertEquals(9_000_000_000_000L, FileUtils.parseSize(" 9000 GB   "));
+        assertEquals(1_234_000_000_000L, FileUtils.parseSize(" 1234 GB  "));
+        assertEquals(1_234_567_890_000L, FileUtils.parseSize(" 1234567890 KB  "));
+    }
+
+    @Test
+    public void testParseSize_invalidArguments() {
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(null));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("null"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(""));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("     "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("KB"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123 dd"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("Invalid"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ABC890 KB  "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-=+90 KB  "));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("--123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-KB"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("++123"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+ 1 +"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+--+ 1 +"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize("1GB+"));
+        assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" + 1234567890 KB  "));
+    }
+
+    @Test
     public void testTranslateMode() throws Exception {
         assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
 
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index ae4edb9..52846df 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -72,4 +72,7 @@
         assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
     }
 
+    public void testGetAdvertisedMem() {
+        assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f4dda4c..80cdd1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -484,12 +484,14 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
         return Optional.ofNullable(
                 RecentTasksController.create(context, shellInit, shellCommandHandler,
-                        taskStackListener, desktopModeTaskRepository, mainExecutor));
+                        taskStackListener, activityTaskManager, desktopModeTaskRepository,
+                        mainExecutor));
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index a5748f6..2a62552 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -17,6 +17,11 @@
 package com.android.wm.shell.recents;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Interface for interacting with the recent tasks.
@@ -29,4 +34,11 @@
     default IRecentTasks createExternalInterface() {
         return null;
     }
+
+    /**
+     * Gets the set of recent tasks.
+     */
+    default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
+            Consumer<List<GroupedRecentTaskInfo>> callback) {
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 6409e70..02b5a35 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -58,6 +58,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Manages the recent task list from the system, caching it as necessary.
@@ -72,6 +74,7 @@
     private final ShellExecutor mMainExecutor;
     private final TaskStackListenerImpl mTaskStackListener;
     private final RecentTasks mImpl = new RecentTasksImpl();
+    private final ActivityTaskManager mActivityTaskManager;
     private IRecentTasksListener mListener;
     private final boolean mIsDesktopMode;
 
@@ -96,6 +99,7 @@
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             @ShellMainThread ShellExecutor mainExecutor
     ) {
@@ -103,17 +107,19 @@
             return null;
         }
         return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
-                desktopModeTaskRepository, mainExecutor);
+                activityTaskManager, desktopModeTaskRepository, mainExecutor);
     }
 
     RecentTasksController(Context context,
             ShellInit shellInit,
             ShellCommandHandler shellCommandHandler,
             TaskStackListenerImpl taskStackListener,
+            ActivityTaskManager activityTaskManager,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
             ShellExecutor mainExecutor) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
+        mActivityTaskManager = activityTaskManager;
         mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
         mTaskStackListener = taskStackListener;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -270,15 +276,10 @@
     }
 
     @VisibleForTesting
-    List<ActivityManager.RecentTaskInfo> getRawRecentTasks(int maxNum, int flags, int userId) {
-        return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId);
-    }
-
-    @VisibleForTesting
     ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
         // Note: the returned task list is from the most-recent to least-recent order
-        final List<ActivityManager.RecentTaskInfo> rawList = getRawRecentTasks(maxNum, flags,
-                userId);
+        final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+                maxNum, flags, userId);
 
         // Make a mapping of task id -> task info
         final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
@@ -335,8 +336,9 @@
         if (componentName == null) {
             return null;
         }
-        List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE,
-                ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
+        List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+                Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+                ActivityManager.getCurrentUser());
         for (int i = 0; i < tasks.size(); i++) {
             final ActivityManager.RecentTaskInfo task = tasks.get(i);
             if (task.isVisible) {
@@ -374,6 +376,16 @@
             mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
             return mIRecentTasks;
         }
+
+        @Override
+        public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
+                Consumer<List<GroupedRecentTaskInfo>> callback) {
+            mMainExecutor.execute(() -> {
+                List<GroupedRecentTaskInfo> tasks =
+                        RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
+                executor.execute(() -> callback.accept(tasks));
+            });
+        }
     }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9c7131a..8b36db920 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -240,6 +241,7 @@
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.IS_SUPPORTED
+                && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 2ccf121..f9d1e88 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -46,6 +46,66 @@
     }
 }
 
+fun FlickerTestParameter.splitScreenEntered(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    fromOtherApp: Boolean
+) {
+    if (fromOtherApp) {
+        appWindowIsInvisibleAtStart(component1)
+    } else {
+        appWindowIsVisibleAtStart(component1)
+    }
+    appWindowIsInvisibleAtStart(component2)
+    splitScreenDividerIsInvisibleAtStart()
+
+    appWindowIsVisibleAtEnd(component1)
+    appWindowIsVisibleAtEnd(component2)
+    splitScreenDividerIsVisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDismissed(
+    component1: IComponentMatcher,
+    component2: IComponentMatcher,
+    toHome: Boolean
+) {
+    appWindowIsVisibleAtStart(component1)
+    appWindowIsVisibleAtStart(component2)
+    splitScreenDividerIsVisibleAtStart()
+
+    appWindowIsInvisibleAtEnd(component1)
+    if (toHome) {
+        appWindowIsInvisibleAtEnd(component2)
+    } else {
+        appWindowIsVisibleAtEnd(component2)
+    }
+    splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+    assertLayersStart {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+    assertLayersEnd {
+        this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+    assertLayersStart {
+        this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+    assertLayersEnd {
+        this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+    }
+}
+
 fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
     layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 }
@@ -271,6 +331,14 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsVisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowVisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowIsVisibleAtEnd(
     component: IComponentMatcher
 ) {
@@ -279,6 +347,22 @@
     }
 }
 
+fun FlickerTestParameter.appWindowIsInvisibleAtStart(
+    component: IComponentMatcher
+) {
+    assertWmStart {
+        this.isAppWindowInvisible(component)
+    }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtEnd(
+    component: IComponentMatcher
+) {
+    assertWmEnd {
+        this.isAppWindowInvisible(component)
+    }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(
     component: IComponentMatcher
 ) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 7dbd279..5b044e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -27,9 +27,13 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -64,36 +68,44 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(textEditApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
         textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 3646fd7..838026f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -33,6 +33,7 @@
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -75,25 +76,25 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsFullscreenAtEnd() {
@@ -116,12 +117,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 80abedd..a764206 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -64,13 +65,15 @@
                     .waitForAndVerify()
             }
         }
-
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
@@ -86,18 +89,15 @@
     fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 2915787..ba02317 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -28,9 +28,13 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.appWindowKeepVisible
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -63,14 +67,27 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerVisibilityChanges() {
@@ -83,23 +100,19 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
         primaryApp, landscapePosLeft = true, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 8e041a7..bb44789 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -82,13 +83,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -98,12 +102,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -127,24 +129,20 @@
         testSpec.layerBecomesVisible(secondaryApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 2ee12f1..e208196 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -34,6 +34,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -87,13 +88,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -103,12 +107,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -132,24 +134,20 @@
         testSpec.layerBecomesVisible(sendNotificationApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         sendNotificationApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index a11874e..84d2e6a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -35,6 +35,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -85,13 +86,16 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() {
         Assume.assumeFalse(isShellTransitionsEnabled)
         testSpec.splitScreenDividerBecomesVisible()
     }
 
     // TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -101,12 +105,10 @@
         }
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() {
@@ -130,24 +132,20 @@
         testSpec.layerBecomesVisible(secondaryApp)
     }
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = false, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
         secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 6064b52..23623aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -31,6 +31,7 @@
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -71,36 +72,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index f06dd66f6..025bb408 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -31,9 +31,12 @@
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -111,19 +114,31 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(primaryApp)
+        testSpec.appWindowIsVisibleAtStart(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+
+        // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+        // robust enough to get the correct end state.
+    }
+
+    @Presubmit
+    @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -136,12 +151,10 @@
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 5c30116..9947a53 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,36 +70,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9c66a37..3716dc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -68,36 +69,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index e8862bd..db07f21 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -30,6 +30,7 @@
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,36 +71,34 @@
     @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+    @Presubmit
+    @Test
     fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
 
-    @IwTest(focusArea = "sysui")
     @Presubmit
     @Test
     fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 70fee2b..b8aaaa7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -40,6 +40,7 @@
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Rect;
@@ -52,7 +53,6 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,7 +68,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
+import java.util.function.Consumer;
 
 /**
  * Tests for {@link RecentTasksController}.
@@ -85,11 +87,13 @@
     private ShellCommandHandler mShellCommandHandler;
     @Mock
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    @Mock
+    private ActivityTaskManager mActivityTaskManager;
 
     private ShellTaskOrganizer mShellTaskOrganizer;
     private RecentTasksController mRecentTasksController;
     private ShellInit mShellInit;
-    private ShellExecutor mMainExecutor;
+    private TestShellExecutor mMainExecutor;
 
     @Before
     public void setUp() {
@@ -97,8 +101,8 @@
         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
         mShellInit = spy(new ShellInit(mMainExecutor));
         mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
-                mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
-                mMainExecutor));
+                mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+                Optional.of(mDesktopModeTaskRepository), mMainExecutor));
         mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
                 null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
                 mMainExecutor);
@@ -188,6 +192,37 @@
     }
 
     @Test
+    public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
+        @SuppressWarnings("unchecked")
+        final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
+        Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+        ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+        ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+        ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+        ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+        ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+        ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+        setRawList(t1, t2, t3, t4, t5, t6);
+
+        // Mark a couple pairs [t2, t4], [t3, t5]
+        SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
+        SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+
+        mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
+        mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
+
+        mRecentTasksController.asRecentTasks()
+                .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+        mMainExecutor.flushAll();
+
+        assertGroupedTasksListEquals(recentTasks[0],
+                t1.taskId, -1,
+                t2.taskId, t4.taskId,
+                t3.taskId, t5.taskId,
+                t6.taskId, -1);
+    }
+
+    @Test
     public void testGetRecentTasks_groupActiveFreeformTasks() {
         StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
                 DesktopModeStatus.class).startMocking();
@@ -296,7 +331,7 @@
         for (ActivityManager.RecentTaskInfo task : tasks) {
             rawList.add(task);
         }
-        doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(),
+        doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
                 anyInt());
         return rawList;
     }
@@ -307,7 +342,7 @@
      * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
      *                        the grouped task list
      */
-    private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks,
+    private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
             int... expectedTaskIds) {
         int[] flattenedTaskIds = new int[recentTasks.size() * 2];
         for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 7acb8c7..a991a71f 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -74,4 +74,8 @@
     symbol_file: "libamidi.map.txt",
 
     first_version: "29",
+    export_header_libs: [
+        "amidi",
+    ],
+
 }
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 32b7a07..8594ba5 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -27,6 +27,9 @@
     symbol_file: "libandroid.map.txt",
     first_version: "9",
     unversioned_until: "current",
+    export_header_libs: [
+        "libandroid_headers",
+    ],
 }
 
 cc_defaults {
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index ef3db4a..b4b65d4 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "SpaLibTests"
+    },
+    {
+      "name": "SpaPrivilegedLibTests"
     }
   ]
 }
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index f8667ed..6384cad 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -22,7 +22,7 @@
     }
 }
 plugins {
-    id 'com.android.application' version '7.3.0-rc01' apply false
-    id 'com.android.library' version '7.3.0-rc01' apply false
+    id 'com.android.application' version '7.3.0' apply false
+    id 'com.android.library' version '7.3.0' apply false
     id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
index 1ebc5da..332d5a8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
@@ -18,8 +18,4 @@
 
 import com.android.settingslib.spa.framework.DebugActivity
 
-class GalleryDebugActivity : DebugActivity(
-    SpaEnvironment.EntryRepository,
-    browseActivityClass = MainActivity::class.java,
-    entryProviderAuthorities = "com.android.spa.gallery.provider",
-)
+class GalleryDebugActivity : DebugActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
index d3e0096..5e04861 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
@@ -18,7 +18,4 @@
 
 import com.android.settingslib.spa.framework.EntryProvider
 
-class GalleryEntryProvider : EntryProvider(
-    SpaEnvironment.EntryRepository,
-    browseActivityClass = MainActivity::class.java,
-)
+class GalleryEntryProvider : EntryProvider(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
similarity index 89%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 6fe88e1..33c4d77 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -18,9 +18,9 @@
 
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -62,9 +62,8 @@
     )
 }
 
-object SpaEnvironment {
-    val PageProviderRepository: SettingsPageProviderRepository by
-    lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+object GallerySpaEnvironment : SpaEnvironment() {
+    override val pageProviderRepository = lazy {
         SettingsPageProviderRepository(
             allPageProviders = listOf(
                 HomePageProvider,
@@ -88,9 +87,7 @@
         )
     }
 
-    val EntryRepository: SettingsEntryRepository by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
-        SettingsEntryRepository(PageProviderRepository)
-    }
+    override val browseActivityClass = MainActivity::class.java
 
-    // TODO: add other environment setup here.
+    override val entryProviderAuthorities = "com.android.spa.gallery.provider"
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index a063847..5e859ce 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -18,4 +18,4 @@
 
 import com.android.settingslib.spa.framework.BrowseActivity
 
-class MainActivity : BrowseActivity(SpaEnvironment.PageProviderRepository)
+class MainActivity : BrowseActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 0bb631a..138ea02 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -31,7 +31,7 @@
 import androidx.navigation.compose.rememberNavController
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.util.navRoute
@@ -48,9 +48,9 @@
  *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
  *   $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
  */
-open class BrowseActivity(
-    private val sppRepository: SettingsPageProviderRepository,
-) : ComponentActivity() {
+open class BrowseActivity(spaEnvironment: SpaEnvironment) : ComponentActivity() {
+    private val sppRepository by spaEnvironment.pageProviderRepository
+
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index c698d9c..85fc366 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -35,8 +35,8 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
 import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.compose.localNavController
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.toState
@@ -61,11 +61,9 @@
  * For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity
  * For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity
  */
-open class DebugActivity(
-    private val entryRepository: SettingsEntryRepository,
-    private val browseActivityClass: Class<*>,
-    private val entryProviderAuthorities: String? = null,
-) : ComponentActivity() {
+open class DebugActivity(private val spaEnvironment: SpaEnvironment) : ComponentActivity() {
+    private val entryRepository by spaEnvironment.entryRepository
+
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
@@ -79,7 +77,7 @@
     }
 
     private fun displayDebugMessage() {
-        if (entryProviderAuthorities == null) return
+        val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return
 
         try {
             val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY
@@ -216,7 +214,7 @@
         if (page.hasRuntimeParam()) return null
         val context = LocalContext.current
         val route = page.buildRoute()
-        val intent = Intent(context, browseActivityClass).apply {
+        val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
             putExtra(KEY_DESTINATION, route)
         }
         return {
@@ -230,7 +228,7 @@
         if (entry.hasRuntimeParam()) return null
         val context = LocalContext.current
         val route = entry.buildRoute()
-        val intent = Intent(context, browseActivityClass).apply {
+        val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
             putExtra(KEY_DESTINATION, route)
         }
         return {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index d923c1c..f0ec83b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -28,8 +28,8 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.util.Log
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
 import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
 
 /**
  * The content provider to return entry related data, which can be used for search and hierarchy.
@@ -42,10 +42,9 @@
  *   $ adb shell content query --uri content://<AuthorityPath>/page_info
  *   $ adb shell content query --uri content://<AuthorityPath>/entry_info
  */
-open class EntryProvider(
-    private val entryRepository: SettingsEntryRepository,
-    private val browseActivityClass: Class<*>? = null,
-) : ContentProvider() {
+open class EntryProvider(spaEnvironment: SpaEnvironment) : ContentProvider() {
+    private val entryRepository by spaEnvironment.entryRepository
+    private val browseActivityClass = spaEnvironment.browseActivityClass
 
     /**
      * Enum to define all column names in provider.
@@ -220,7 +219,7 @@
     }
 
     private fun createBrowsePageIntent(page: SettingsPage): Intent {
-        if (context == null || browseActivityClass == null || page.hasRuntimeParam())
+        if (context == null || page.hasRuntimeParam())
             return Intent()
 
         return Intent().setComponent(ComponentName(context!!, browseActivityClass)).apply {
@@ -231,8 +230,7 @@
     private fun createBrowsePageAdbCommand(page: SettingsPage): String? {
         if (context == null || page.hasRuntimeParam()) return null
         val packageName = context!!.packageName
-        val activityName =
-            browseActivityClass?.name?.replace(packageName, "") ?: "<browse-activity-class>"
+        val activityName = browseActivityClass.name.replace(packageName, "")
         return "adb shell am start -n $packageName/$activityName" +
             " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
new file mode 100644
index 0000000..111555b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.settingslib.spa.framework.common
+
+import android.app.Activity
+
+abstract class SpaEnvironment {
+    abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository>
+
+    val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+
+    abstract val browseActivityClass: Class<out Activity>
+
+    open val entryProviderAuthorities: String? = null
+
+    // TODO: add other environment setup here.
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 082ce97..e7e37e4 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -45,3 +45,9 @@
         "-J-Xmx4G",
     ],
 }
+
+// Expose the srcs to tests, so the tests can access the internal classes.
+filegroup {
+    name: "SpaPrivilegedLib_srcs",
+    srcs: ["src/**/*.kt"],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
similarity index 80%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index bb94b33..ee89003 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,7 +21,6 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.content.pm.UserInfo
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.coroutineScope
@@ -30,14 +29,25 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 
-class AppsRepository(context: Context) {
+/**
+ * The config used to load the App List.
+ */
+internal data class AppListConfig(
+    val userId: Int,
+    val showInstantApps: Boolean,
+)
+
+/**
+ * The repository to load the App List data.
+ */
+internal class AppListRepository(context: Context) {
     private val packageManager = context.packageManager
 
-    fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow
+    fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
         .map { loadApps(it) }
         .flowOn(Dispatchers.Default)
 
-    private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> {
+    private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
         return coroutineScope {
             val hiddenSystemModulesDeferred = async {
                 packageManager.getInstalledModules(0)
@@ -50,11 +60,11 @@
                     PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
             )
             val installedApplicationsAsUser =
-                packageManager.getInstalledApplicationsAsUser(flags, userInfo.id)
+                packageManager.getInstalledApplicationsAsUser(flags, config.userId)
 
             val hiddenSystemModules = hiddenSystemModulesDeferred.await()
             installedApplicationsAsUser.filter { app ->
-                app.isInAppList(hiddenSystemModules)
+                app.isInAppList(config.showInstantApps, hiddenSystemModules)
             }
         }
     }
@@ -63,9 +73,7 @@
         userIdFlow: Flow<Int>,
         showSystemFlow: Flow<Boolean>,
     ): Flow<(app: ApplicationInfo) -> Boolean> =
-        userIdFlow.combine(showSystemFlow) { userId, showSystem ->
-            showSystemPredicate(userId, showSystem)
-        }
+        userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
 
     private suspend fun showSystemPredicate(
         userId: Int,
@@ -102,12 +110,15 @@
     }
 
     companion object {
-        private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) =
-            when {
-                packageName in hiddenSystemModules -> false
-                enabled -> true
-                enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
-                else -> false
-            }
+        private fun ApplicationInfo.isInAppList(
+            showInstantApps: Boolean,
+            hiddenSystemModules: Set<String>,
+        ) = when {
+            !showInstantApps && isInstantApp -> false
+            packageName in hiddenSystemModules -> false
+            enabled -> true
+            isDisabledUntilUsed -> true
+            else -> false
+        }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 9265158..1e487da 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -18,7 +18,6 @@
 
 import android.app.Application
 import android.content.pm.ApplicationInfo
-import android.content.pm.UserInfo
 import android.icu.text.Collator
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
@@ -48,28 +47,29 @@
 internal class AppListViewModel<T : AppRecord>(
     application: Application,
 ) : AndroidViewModel(application) {
-    val userInfo = StateFlowBridge<UserInfo>()
+    val appListConfig = StateFlowBridge<AppListConfig>()
     val listModel = StateFlowBridge<AppListModel<T>>()
     val showSystem = StateFlowBridge<Boolean>()
     val option = StateFlowBridge<Int>()
     val searchQuery = StateFlowBridge<String>()
 
-    private val appsRepository = AppsRepository(application)
+    private val appListRepository = AppListRepository(application)
     private val appRepository = AppRepositoryImpl(application)
     private val collator = Collator.getInstance().freeze()
     private val labelMap = ConcurrentHashMap<String, String>()
     private val scope = viewModelScope + Dispatchers.Default
 
-    private val userIdFlow = userInfo.flow.map { it.id }
+    private val userIdFlow = appListConfig.flow.map { it.userId }
 
     private val recordListFlow = listModel.flow
-        .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) }
+        .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
         .shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
 
-    private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow)
-        .combine(recordListFlow) { showAppPredicate, recordList ->
-            recordList.filter { showAppPredicate(it.app) }
-        }
+    private val systemFilteredFlow =
+        appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+            .combine(recordListFlow) { showAppPredicate, recordList ->
+                recordList.filter { showAppPredicate(it.app) }
+            }
 
     val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
         .combine(searchQuery.flow) { appListData, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
similarity index 90%
rename from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
rename to packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index 96e3e77..f9f75fb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -32,8 +32,8 @@
 fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
 
 /** Checks whether the application is disabled until used. */
-fun ApplicationInfo.isDisabledUntilUsed(): Boolean =
-    enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+val ApplicationInfo.isDisabledUntilUsed: Boolean
+    get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
 
 /** Converts to the route string which used in navigation. */
 fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 315dc5d..6318b4e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spaprivileged.template.app
 
-import android.content.pm.UserInfo
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.lazy.LazyColumn
@@ -33,6 +32,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListData
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppListViewModel
@@ -41,17 +41,22 @@
 
 private const val TAG = "AppList"
 
+/**
+ * The template to render an App List.
+ *
+ * This UI element will take the remaining space on the screen to show the App List.
+ */
 @Composable
 internal fun <T : AppRecord> AppList(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
-    LogCompositions(TAG, userInfo.id.toString())
-    val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery)
+    LogCompositions(TAG, appListConfig.userId.toString())
+    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
     AppListWidget(appListData, listModel, appItem)
 }
 
@@ -85,14 +90,14 @@
 
 @Composable
 private fun <T : AppRecord> loadAppEntries(
-    userInfo: UserInfo,
+    appListConfig: AppListConfig,
     listModel: AppListModel<T>,
     showSystem: State<Boolean>,
     option: State<Int>,
     searchQuery: State<String>,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString())
-    viewModel.userInfo.setIfAbsent(userInfo)
+    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
+    viewModel.appListConfig.setIfAbsent(appListConfig)
     viewModel.listModel.setIfAbsent(listModel)
     viewModel.showSystem.Sync(showSystem)
     viewModel.option.Sync(option)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index d537ec2..fb03d2c 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -36,14 +36,19 @@
 import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
 import com.android.settingslib.spa.widget.ui.Spinner
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
 
+/**
+ * The full screen template for an App List page.
+ */
 @Composable
 fun <T : AppRecord> AppListPage(
     title: String,
     listModel: AppListModel<T>,
+    showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
@@ -62,7 +67,10 @@
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    userInfo = userInfo,
+                    appListConfig = AppListConfig(
+                        userId = userInfo.id,
+                        showInstantApps = showInstantApps,
+                    ),
                     listModel = listModel,
                     showSystem = showSystem,
                     option = selectedOption,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
new file mode 100644
index 0000000..940a1fe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -0,0 +1,46 @@
+//
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SpaPrivilegedLibTests",
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    srcs: [
+        ":SpaPrivilegedLib_srcs",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "SpaPrivilegedLib",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "mockito-target-minus-junit4",
+        "truth-prebuilt",
+    ],
+    kotlincflags: [
+        "-Xjvm-default=all",
+        "-Xopt-in=kotlin.RequiresOptIn",
+    ],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
new file mode 100644
index 0000000..c4f490e
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.spaprivileged.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Tests for SpaPrivilegedLib"
+        android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
new file mode 100644
index 0000000..c010c68
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.PackageManager.ResolveInfoFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val USER_ID = 0
+
+@RunWith(AndroidJUnit4::class)
+class AppListRepositoryTest {
+
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private lateinit var repository: AppListRepository
+
+    private val normalApp = ApplicationInfo().apply {
+        packageName = "normal"
+        enabled = true
+    }
+
+    private val instantApp = ApplicationInfo().apply {
+        packageName = "instant"
+        enabled = true
+        privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+    }
+
+    @Before
+    fun setUp() {
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
+        whenever(
+            packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+        ).thenReturn(listOf(normalApp, instantApp))
+        whenever(
+            packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+        ).thenReturn(emptyList())
+
+        repository = AppListRepository(context)
+    }
+
+    @Test
+    fun notShowInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp)
+        }
+    }
+
+    @Test
+    fun showInstantApps(): Unit = runBlocking {
+        val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
+
+        val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+        launch {
+            val flowValues = mutableListOf<List<ApplicationInfo>>()
+            appListFlow.toList(flowValues)
+            assertThat(flowValues).hasSize(1)
+
+            assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
+        }
+    }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index cdedc64..9766514 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -93,8 +93,8 @@
             Futures.addCallback(
                 captureToBitmap(window),
                 object : FutureCallback<Bitmap> {
-                    override fun onSuccess(result: Bitmap) {
-                        continuation.resumeWith(Result.success(result))
+                    override fun onSuccess(result: Bitmap?) {
+                        continuation.resumeWith(Result.success(result!!))
                     }
 
                     override fun onFailure(t: Throwable) {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 59c6635..2b381a9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -42,7 +42,7 @@
     }
 
     private fun List<RecentTask>.sortTasks(): List<RecentTask> =
-        asReversed().sortedBy {
+        sortedBy {
             // Show normal tasks first and only then tasks with opened app selector
             it.topActivityComponent == appSelectorComponentName
         }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
index 0bdddfe..0927f3b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -17,11 +17,17 @@
 package com.android.systemui.mediaprojection.appselector.data
 
 import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
 import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.IconFactory
 import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
-import javax.inject.Inject
 
 interface AppIconLoader {
     suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
@@ -31,11 +37,20 @@
 @Inject
 constructor(
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val context: Context,
+    private val packageManager: PackageManager
 ) : AppIconLoader {
 
     override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
         withContext(backgroundDispatcher) {
-            // TODO(b/240924731): add a blocking call to load an icon using iconloaderlib
-            null
+            IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
+                val activityInfo = packageManager
+                        .getActivityInfo(component, ComponentInfoFlags.of(0))
+                val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
+                val userHandler = UserHandle.of(userId)
+                val options = IconOptions().apply { setUser(userHandler) }
+                val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+                badgedIcon.newIcon(context)
+            }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 6d67e28..cd994b8 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.mediaprojection.appselector.data
 
+import android.annotation.ColorInt
 import android.content.ComponentName
 
 data class RecentTask(
     val taskId: Int,
     val userId: Int,
     val topActivityComponent: ComponentName?,
-    val baseIntentComponent: ComponentName?
+    val baseIntentComponent: ComponentName?,
+    @ColorInt val colorBackground: Int?
 )
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5a09435..e8b49cd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,23 +16,61 @@
 
 package com.android.systemui.mediaprojection.appselector.data
 
+import android.app.ActivityManager
+import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
-import javax.inject.Inject
+import java.util.concurrent.Executor
 
 interface RecentTaskListProvider {
+    /** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
     suspend fun loadRecentTasks(): List<RecentTask>
 }
 
 class ShellRecentTaskListProvider
 @Inject
-constructor(@Background private val coroutineDispatcher: CoroutineDispatcher) :
-    RecentTaskListProvider {
+constructor(
+    @Background private val coroutineDispatcher: CoroutineDispatcher,
+    @Background private val backgroundExecutor: Executor,
+    private val recentTasks: Optional<RecentTasks>
+) : RecentTaskListProvider {
+
+    private val recents by lazy { recentTasks.getOrNull() }
 
     override suspend fun loadRecentTasks(): List<RecentTask> =
         withContext(coroutineDispatcher) {
-            // TODO(b/240924731): add blocking call to load the recents
-            emptyList()
+            val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+
+            rawRecentTasks
+                .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
+                .map {
+                    RecentTask(
+                        it.taskId,
+                        it.userId,
+                        it.topActivity,
+                        it.baseIntent?.component,
+                        it.taskDescription?.backgroundColor
+                    )
+                }
+        }
+
+    private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
+        suspendCoroutine { continuation ->
+            getRecentTasks(
+                Integer.MAX_VALUE,
+                RECENT_IGNORE_UNAVAILABLE,
+                ActivityManager.getCurrentUser(),
+                backgroundExecutor
+            ) { tasks ->
+                continuation.resume(tasks)
+            }
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
index 4291280..47faaed 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.withContext
@@ -30,12 +31,13 @@
 @Inject
 constructor(
     @Background private val coroutineDispatcher: CoroutineDispatcher,
-) :
-    RecentTaskThumbnailLoader {
+    private val activityManager: ActivityManagerWrapper
+) : RecentTaskThumbnailLoader {
 
     override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
         withContext(coroutineDispatcher) {
-            // TODO(b/240924731): add blocking call to load a thumbnail
-             null
+            val thumbnailData =
+                activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
+            if (thumbnailData.thumbnail == null) null else thumbnailData
         }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 37b7f47..00b1f32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -54,7 +54,7 @@
     }
 
     @Test
-    fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInReverse() {
+    fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
         val tasks = listOf(
             createRecentTask(taskId = 1),
             createRecentTask(taskId = 2),
@@ -66,15 +66,15 @@
 
         verify(view).bind(
             listOf(
-                createRecentTask(taskId = 3),
-                createRecentTask(taskId = 2),
                 createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
             )
         )
     }
 
     @Test
-    fun initRecentTasksWithAppSelectorTasks_bindsListInReverseAndAppSelectorTasksAtTheEnd() {
+    fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
         val tasks = listOf(
             createRecentTask(taskId = 1),
             createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
@@ -88,11 +88,11 @@
 
         verify(view).bind(
             listOf(
-                createRecentTask(taskId = 5),
-                createRecentTask(taskId = 3),
                 createRecentTask(taskId = 1),
-                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 5),
                 createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
+                createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
             )
         )
     }
@@ -105,7 +105,8 @@
             taskId = taskId,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
-            userId = 0
+            userId = 0,
+            colorBackground = 0
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
new file mode 100644
index 0000000..939af16
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.app.ActivityManager.RecentTaskInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.google.common.truth.Truth.assertThat
+import java.util.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShellRecentTaskListProviderTest : SysuiTestCase() {
+
+    private val dispatcher = Dispatchers.Unconfined
+    private val recentTasks: RecentTasks = mock()
+    private val recentTaskListProvider =
+        ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+
+    @Test
+    fun loadRecentTasks_oneTask_returnsTheSameTask() {
+        givenRecentTasks(createSingleTask(taskId = 1))
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result).containsExactly(createRecentTask(taskId = 1))
+    }
+
+    @Test
+    fun loadRecentTasks_multipleTasks_returnsTheSameTasks() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createSingleTask(taskId = 2),
+            createSingleTask(taskId = 3),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
+            )
+    }
+
+    @Test
+    fun loadRecentTasks_groupedTask_returnsUngroupedTasks() {
+        givenRecentTasks(createTaskPair(taskId1 = 1, taskId2 = 2))
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
+    }
+
+    @Test
+    fun loadRecentTasks_mixedSingleAndGroupedTask_returnsUngroupedTasks() {
+        givenRecentTasks(
+            createSingleTask(taskId = 1),
+            createTaskPair(taskId1 = 2, taskId2 = 3),
+            createSingleTask(taskId = 4),
+            createTaskPair(taskId1 = 5, taskId2 = 6),
+        )
+
+        val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+        assertThat(result)
+            .containsExactly(
+                createRecentTask(taskId = 1),
+                createRecentTask(taskId = 2),
+                createRecentTask(taskId = 3),
+                createRecentTask(taskId = 4),
+                createRecentTask(taskId = 5),
+                createRecentTask(taskId = 6),
+            )
+    }
+
+    @Suppress("UNCHECKED_CAST")
+    private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+        whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
+            val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+            consumer.accept(tasks.toList())
+        }
+    }
+
+    private fun createRecentTask(taskId: Int): RecentTask =
+        RecentTask(
+            taskId = taskId,
+            userId = 0,
+            topActivityComponent = null,
+            baseIntentComponent = null,
+            colorBackground = null
+        )
+
+    private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
+
+    private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
+        GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
+
+    private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 838cbd9..ec30369b 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -24,7 +24,6 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -42,6 +41,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
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/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 992d416..d7b3848 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,6 +27,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.getAdvertisedMem;
 import static android.os.Process.getFreeMemory;
 import static android.os.Process.getTotalMemory;
 import static android.os.Process.killProcessQuiet;
@@ -1532,6 +1533,7 @@
     void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
         final long homeAppMem = getMemLevel(HOME_APP_ADJ);
         final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ);
+        outInfo.advertisedMem = getAdvertisedMem();
         outInfo.availMem = getFreeMemory();
         outInfo.totalMem = getTotalMemory();
         outInfo.threshold = homeAppMem;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 072d17f..403ad9b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -105,6 +105,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.PackageTagsList;
 import android.os.Process;
@@ -374,10 +375,17 @@
     public AppOpsUidStateTracker getUidStateTracker() {
         if (mUidStateTracker == null) {
             mUidStateTracker = new AppOpsUidStateTrackerImpl(
-                    LocalServices.getService(ActivityManagerInternal.class), mHandler,
+                    LocalServices.getService(ActivityManagerInternal.class),
+                    mHandler,
+                    r -> {
+                        synchronized (AppOpsService.this) {
+                            r.run();
+                        }
+                    },
                     Clock.SYSTEM_CLOCK, mConstants);
 
-            mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged);
+            mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+                    this::onUidStateChanged);
         }
         return mUidStateTracker;
     }
@@ -4809,6 +4817,8 @@
         pw.println("    Only output the watcher sections.");
         pw.println("  --history");
         pw.println("    Only output history.");
+        pw.println("  --uid-state-change-logs");
+        pw.println("    Include logs about uid state changes.");
     }
 
     private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@@ -4946,6 +4956,7 @@
         // TODO ntmyren: Remove the dumpHistory and dumpFilter
         boolean dumpHistory = false;
         boolean includeDiscreteOps = false;
+        boolean dumpUidStateChangeLogs = false;
         int nDiscreteOps = 10;
         @HistoricalOpsRequestFilter int dumpFilter = 0;
         boolean dumpAll = false;
@@ -5028,6 +5039,8 @@
                 } else if (arg.length() > 0 && arg.charAt(0) == '-') {
                     pw.println("Unknown option: " + arg);
                     return;
+                } else if ("--uid-state-change-logs".equals(arg)) {
+                    dumpUidStateChangeLogs = true;
                 } else {
                     pw.println("Unknown command: " + arg);
                     return;
@@ -5363,6 +5376,12 @@
                     pw.println("  AppOps policy not set.");
                 }
             }
+
+            if (dumpAll || dumpUidStateChangeLogs) {
+                pw.println();
+                pw.println("Uid State Changes Event Log:");
+                getUidStateTracker().dumpEvents(pw);
+            }
         }
 
         // Must not hold the appops lock
@@ -5375,14 +5394,6 @@
             mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
                     dumpFilter, dumpOp, sdf, date, "  ", nDiscreteOps);
         }
-
-        if (dumpAll) {
-            pw.println();
-            pw.println("Uid State Changes Event Log:");
-            if (mUidStateTracker != null) {
-                mUidStateTracker.dumpEvents(pw);
-            }
-        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 3da121b..742bf4b 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -30,10 +30,11 @@
 import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
 import static android.app.AppOpsManager.UID_STATE_TOP;
 
-import android.os.Handler;
+import android.annotation.CallbackExecutor;
 import android.util.SparseArray;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 interface AppOpsUidStateTracker {
 
@@ -95,7 +96,7 @@
     /**
      * Listen to changes in {@link android.app.AppOpsManager.UidState}
      */
-    void addUidStateChangedCallback(Handler handler,
+    void addUidStateChangedCallback(@CallbackExecutor Executor executor,
             UidStateChangedCallback callback);
 
     /**
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 52b67b5..3c281d1 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -44,17 +44,18 @@
 import android.util.SparseLongArray;
 import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.Clock;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.PrintWriter;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
 
     private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName();
 
-    private final Handler mHandler;
+    private final DelayableExecutor mExecutor;
     private final Clock mClock;
     private ActivityManagerInternal mActivityManagerInternal;
     private AppOpsService.Constants mConstants;
@@ -68,18 +69,46 @@
     private SparseLongArray mPendingCommitTime = new SparseLongArray();
     private SparseBooleanArray mPendingGone = new SparseBooleanArray();
 
-    private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
+    private ArrayMap<UidStateChangedCallback, Executor>
+            mUidStateChangedCallbacks = new ArrayMap<>();
 
     private final EventLog mEventLog;
 
+    @VisibleForTesting
+    interface DelayableExecutor extends Executor {
+
+        void execute(Runnable runnable);
+
+        void executeDelayed(Runnable runnable, long delay);
+    }
+
     AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
-            Handler handler, Clock clock, AppOpsService.Constants constants) {
+            Handler handler, Executor lockingExecutor, Clock clock,
+            AppOpsService.Constants constants) {
+
+        this(activityManagerInternal, new DelayableExecutor() {
+            @Override
+            public void execute(Runnable runnable) {
+                handler.post(() -> lockingExecutor.execute(runnable));
+            }
+
+            @Override
+            public void executeDelayed(Runnable runnable, long delay) {
+                handler.postDelayed(() -> lockingExecutor.execute(runnable), delay);
+            }
+        }, clock, constants, handler.getLooper().getThread());
+    }
+
+    @VisibleForTesting
+    AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
+            DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+            Thread executorThread) {
         mActivityManagerInternal = activityManagerInternal;
-        mHandler = handler;
+        mExecutor = executor;
         mClock = clock;
         mConstants = constants;
 
-        mEventLog = new EventLog(handler);
+        mEventLog = new EventLog(executor, executorThread);
     }
 
     @Override
@@ -157,11 +186,12 @@
     }
 
     @Override
-    public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) {
+    public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) {
         if (mUidStateChangedCallbacks.containsKey(callback)) {
             throw new IllegalStateException("Callback is already registered.");
         }
-        mUidStateChangedCallbacks.put(callback, handler);
+
+        mUidStateChangedCallbacks.put(callback, executor);
     }
 
     @Override
@@ -232,7 +262,7 @@
                 final long commitTime = mClock.elapsedRealtime() + settleTime;
                 mPendingCommitTime.put(uid, commitTime);
 
-                mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+                mExecutor.executeDelayed(PooledLambda.obtainRunnable(
                                 AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this,
                                 uid), settleTime + 1);
             }
@@ -323,10 +353,11 @@
 
             for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
                 UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
-                Handler h = mUidStateChangedCallbacks.valueAt(i);
+                Executor executor = mUidStateChangedCallbacks.valueAt(i);
 
-                h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged,
-                        cb, uid, pendingUidState, foregroundChange));
+                executor.execute(PooledLambda.obtainRunnable(
+                        UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState,
+                        foregroundChange));
             }
         }
 
@@ -366,7 +397,8 @@
         // Memory usage: 24 * size bytes
         private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
 
-        private final Handler mHandler;
+        private final DelayableExecutor mExecutor;
+        private final Thread mExecutorThread;
 
         private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3];
         private long[] mUpdateUidProcStateLogTimestamps =
@@ -384,15 +416,16 @@
         private int mEvalForegroundModeLogSize = 0;
         private int mEvalForegroundModeLogHead = 0;
 
-        EventLog(Handler handler) {
-            mHandler = handler;
+        EventLog(DelayableExecutor executor, Thread executorThread) {
+            mExecutor = executor;
+            mExecutorThread = executorThread;
         }
 
         void logUpdateUidProcState(int uid, int procState, int capability) {
             if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync,
                     this, System.currentTimeMillis(), uid, procState, capability));
         }
 
@@ -416,7 +449,7 @@
             if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, visible));
         }
 
@@ -442,7 +475,7 @@
             if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) {
                 return;
             }
-            mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync,
+            mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync,
                     this, System.currentTimeMillis(), uid, uidState, capability, code, result));
         }
 
@@ -466,22 +499,6 @@
         }
 
         void dumpEvents(PrintWriter pw) {
-            if (Thread.currentThread() != mHandler.getLooper().getThread()) {
-                // All operations are done on the handler's thread
-                CountDownLatch latch = new CountDownLatch(1);
-                mHandler.post(() -> {
-                    dumpEvents(pw);
-                    latch.countDown();
-                });
-
-                try {
-                    latch.await();
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return;
-            }
-
             int updateIdx = 0;
             int commitIdx = 0;
             int evalIdx = 0;
@@ -536,13 +553,13 @@
             pw.print(" UPDATE_UID_PROC_STATE");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" procState=");
             pw.print(String.format("%-30s", ActivityManager.procStateToString(procState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.println();
         }
@@ -559,13 +576,13 @@
             pw.print(" COMMIT_UID_STATE     ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" visibleAppWidget=");
             pw.print(visibleAppWidget);
@@ -586,13 +603,13 @@
             pw.print(" EVAL_FOREGROUND_MODE ");
 
             pw.print(" uid=");
-            pw.print(uid);
+            pw.print(String.format("%-8d", uid));
 
             pw.print(" uidState=");
             pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
 
             pw.print(" capability=");
-            pw.print(ActivityManager.getCapabilitiesSummary(capability));
+            pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
 
             pw.print(" code=");
             pw.print(String.format("%-20s", AppOpsManager.opToName(code)));
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2cde526..e851f03 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -80,7 +80,6 @@
 import android.hardware.display.VirtualDisplayConfig;
 import android.hardware.display.WifiDisplayStatus;
 import android.hardware.graphics.common.DisplayDecorationSupport;
-import android.hardware.input.InputManagerInternal;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionManager;
 import android.net.Uri;
@@ -134,6 +133,7 @@
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.utils.SensorUtils;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.SurfaceAnimationThread;
 import com.android.server.wm.WindowManagerInternal;
 
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 66ec5c4..57c2e01 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -7,6 +7,15 @@
                 {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
                 {"exclude-annotation": "androidx.test.filters.FlakyTest"}
             ]
+        },
+        {
+            "name": "FrameworksServicesTests",
+            "options": [
+                {"include-filter": "com.android.server.display"},
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+                {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+                {"exclude-annotation": "org.junit.Ignore"}
+            ]
         }
     ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fee1f5c..e1b18f2 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -38,7 +38,6 @@
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -66,6 +65,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.ActivityInterceptorCallback;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e03a46c..3ee3503 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -266,10 +266,6 @@
     // Make sure HdmiCecConfig is instantiated and the XMLs are read.
     private HdmiCecConfig mHdmiCecConfig;
 
-    // Last return value of getPhysicalAddress(). Only updated on calls of getPhysicalAddress().
-    // Does not represent the current physical address at all times. Not to be used as a cache.
-    private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
-
     /**
      * Interface to report send result.
      */
@@ -2082,15 +2078,9 @@
         @Override
         public int getPhysicalAddress() {
             initBinderCall();
-            runOnServiceThread(new Runnable() {
-                @Override
-                public void run() {
-                    synchronized (mLock) {
-                        mPhysicalAddress = mHdmiCecNetwork.getPhysicalAddress();
-                    }
-                }
-            });
-            return mPhysicalAddress;
+            synchronized (mLock) {
+                return mHdmiCecNetwork.getPhysicalAddress();
+            }
         }
 
         @Override
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
similarity index 98%
rename from core/java/android/hardware/input/InputManagerInternal.java
rename to services/core/java/com/android/server/input/InputManagerInternal.java
index fc6bc55..36099b0 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.hardware.input;
+package com.android.server.input;
 
 import android.annotation.NonNull;
 import android.graphics.PointF;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 05c4f77..71feb95 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -55,8 +55,6 @@
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
-import android.hardware.input.InputManagerInternal.LidSwitchCallback;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.TouchCalibration;
@@ -123,6 +121,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
+import com.android.server.input.InputManagerInternal.LidSwitchCallback;
 import com.android.server.policy.WindowManagerPolicy;
 
 import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index f89b6ae..a4830be 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -21,7 +21,6 @@
 import android.annotation.AnyThread;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
-import android.hardware.input.InputManagerInternal;
 import android.os.IBinder;
 import android.os.Looper;
 import android.util.Slog;
@@ -35,6 +34,7 @@
 import android.view.SurfaceControl;
 
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed17b9ca..4571546 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -92,7 +92,6 @@
 import android.graphics.Matrix;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManagerInternal;
 import android.net.Uri;
@@ -188,6 +187,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
 import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 9b7d19a..f8fcaff 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -26,7 +26,6 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
 import android.os.Environment;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -39,6 +38,7 @@
 import com.android.server.LocalServices;
 import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
 import com.android.server.policy.devicestate.config.DeviceStateConfig;
 import com.android.server.policy.devicestate.config.Flags;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 02d7074..07f5bcf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -121,7 +121,6 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -204,6 +203,7 @@
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
 import com.android.server.policy.keyguard.KeyguardServiceDelegate;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 9336be5..69fb22c 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -27,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -62,6 +61,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.statusbar.StatusBarManagerInternal;
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 948439d..2d022ae 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -189,6 +189,13 @@
             if (!parent.exists()) {
                 throw new IOException("Failed to create directory " + parent.getCanonicalPath());
             }
+
+            // Give executable permissions to parent folders.
+            while (!(parent.equals(updateDir))) {
+                parent.setExecutable(true, false);
+                parent = parent.getParentFile();
+            }
+
             // create the temporary file
             tmp = File.createTempFile("journal", "", dir);
             // mark tmp -rw-r--r--
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index acbf1a4..6e23ed9 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -391,8 +391,7 @@
      * </p>
      */
     private void handleStartRecordingFailed() {
-        final boolean shouldExitTaskRecording = mContentRecordingSession != null
-                && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+        final boolean shouldExitTaskRecording = isRecordingContentTask();
         clearContentRecordingSession();
         if (shouldExitTaskRecording) {
             // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,9 +477,10 @@
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                 "Recorded task is removed, so stop recording on display %d",
                 mDisplayContent.getDisplayId());
-        Task recordedTask = mRecordedWindowContainer.asTask();
-        if (recordedTask == null
-                || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+
+        Task recordedTask = mRecordedWindowContainer != null
+                ? mRecordedWindowContainer.asTask() : null;
+        if (recordedTask == null || !isRecordingContentTask()) {
             return;
         }
         recordedTask.unregisterWindowContainerListener(this);
@@ -504,4 +504,9 @@
     @VisibleForTesting interface MediaProjectionManagerWrapper {
         void stopActiveProjection();
     }
+
+    private boolean isRecordingContentTask() {
+        return mContentRecordingSession != null
+                && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 78b4ce2..3f380e7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -168,8 +168,9 @@
     jmethodID constructor;
     jfieldID lightTypeInput;
     jfieldID lightTypePlayerId;
+    jfieldID lightTypeKeyboardBacklight;
     jfieldID lightCapabilityBrightness;
-    jfieldID lightCapabilityRgb;
+    jfieldID lightCapabilityColorRgb;
 } gLightClassInfo;
 
 static struct {
@@ -2011,25 +2012,28 @@
 
         jint jTypeId =
                 env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
-        jint jCapability = 0;
-
-        if (lightInfo.type == InputDeviceLightType::MONO) {
-            jCapability = env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityBrightness);
-        } else if (lightInfo.type == InputDeviceLightType::RGB ||
-                   lightInfo.type == InputDeviceLightType::MULTI_COLOR) {
-            jCapability =
-                env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityBrightness) |
-                env->GetStaticIntField(gLightClassInfo.clazz,
-                                                 gLightClassInfo.lightCapabilityRgb);
+        if (lightInfo.type == InputDeviceLightType::INPUT) {
+            jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
         } else if (lightInfo.type == InputDeviceLightType::PLAYER_ID) {
             jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
                                                  gLightClassInfo.lightTypePlayerId);
+        } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+            jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+                                             gLightClassInfo.lightTypeKeyboardBacklight);
         } else {
             ALOGW("Unknown light type %d", lightInfo.type);
             continue;
         }
+
+        jint jCapability = 0;
+        if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)) {
+            jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+                                                  gLightClassInfo.lightCapabilityBrightness);
+        }
+        if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::RGB)) {
+            jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+                                                  gLightClassInfo.lightCapabilityColorRgb);
+        }
         ScopedLocalRef<jobject> lightObj(env,
                                          env->NewObject(gLightClassInfo.clazz,
                                                         gLightClassInfo.constructor,
@@ -2596,10 +2600,12 @@
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT", "I");
     gLightClassInfo.lightTypePlayerId =
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_PLAYER_ID", "I");
+    gLightClassInfo.lightTypeKeyboardBacklight =
+            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I");
     gLightClassInfo.lightCapabilityBrightness =
             env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I");
-    gLightClassInfo.lightCapabilityRgb =
-            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_RGB", "I");
+    gLightClassInfo.lightCapabilityColorRgb =
+            env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_COLOR_RGB", "I");
 
     // ArrayList
     FIND_CLASS(gArrayListClassInfo.clazz, "java/util/ArrayList");
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 6196c49..9c9b363 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1287,8 +1287,8 @@
         bp.set_allocated_dest_path(&target);
         bp.set_allocated_source_subdir(&source);
         const auto metadata = bp.SerializeAsString();
-        bp.release_dest_path();
-        bp.release_source_subdir();
+        static_cast<void>(bp.release_dest_path());
+        static_cast<void>(bp.release_source_subdir());
         mdFileName = makeBindMdName();
         metadataFullPath = path::join(ifs.root, constants().mount, mdFileName);
         auto node = mIncFs->makeFile(ifs.control, metadataFullPath, 0444, idFromMetadata(metadata),
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
index 525a931..c8d153a 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -35,6 +34,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.LocalServices;
 import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.input.InputManagerInternal;
 
 final class SelectionToolbarManagerServiceImpl extends
         AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
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/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 86e12647..e1713b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -34,12 +34,9 @@
 import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -49,25 +46,22 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.Message;
 import android.util.SparseArray;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.Clock;
 import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback;
+import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.quality.Strictness;
 
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.PriorityQueue;
 
 public class AppOpsUidStateTrackerTest {
 
@@ -81,12 +75,11 @@
     ActivityManagerInternal mAmi;
 
     @Mock
-    Handler mHandler;
-
-    @Mock
     AppOpsService.Constants mConstants;
 
-    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock();
+    AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
+
+    AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor);
 
     AppOpsUidStateTracker mIntf;
 
@@ -101,7 +94,8 @@
         mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
         mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
         mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
-        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants);
+        mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants,
+                Thread.currentThread());
     }
 
     @After
@@ -263,18 +257,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -291,18 +277,10 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-        AtomicLong delayAtomicReference = new AtomicLong();
+        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
+        assertForeground(UID);
 
-        getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
-        Message message = messageAtomicReference.get();
-        long delay = delayAtomicReference.get();
-
-        assertNotNull(message);
-        assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay);
-
-        mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1);
-        message.getCallback().run();
+        mClock.advanceTime(1);
         assertBackground(UID);
     }
 
@@ -319,14 +297,8 @@
         // Still in foreground due to settle time
         assertForeground(UID);
 
-        AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-
-        getPostDelayedMessageArguments(messageAtomicReference, null);
-        Message message = messageAtomicReference.get();
-
         // 1 ms short of settle time
         mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
-        message.getCallback().run();
         assertForeground(UID);
     }
 
@@ -471,8 +443,6 @@
                 .topState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true));
     }
 
@@ -484,8 +454,6 @@
                 .foregroundServiceState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true));
     }
 
@@ -497,8 +465,6 @@
                 .foregroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true));
     }
 
@@ -510,8 +476,6 @@
                 .backgroundState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
-
         verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false));
     }
 
@@ -679,7 +643,6 @@
                 .nonExistentState()
                 .update();
 
-        verify(mHandler, never()).post(any());
         verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
     }
 
@@ -695,7 +658,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false));
     }
 
@@ -711,7 +673,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -727,7 +688,6 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
@@ -743,10 +703,32 @@
                 .nonExistentState()
                 .update();
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
     }
 
+    @Test
+    public void testUidStateChangedBackgroundThenForegroundImmediately() {
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        UidStateChangedCallback cb = addUidStateChangeCallback();
+
+        procStateBuilder(UID)
+            .backgroundState()
+            .update();
+
+        mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+
+        procStateBuilder(UID)
+            .topState()
+            .update();
+
+        mClock.advanceTime(1);
+
+        verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+    }
+
     public void testUidStateChangedCallback(int initialState, int finalState) {
         int initialUidState = processStateToUidState(initialState);
         int finalUidState = processStateToUidState(finalState);
@@ -767,13 +749,9 @@
                 .update();
 
         if (finalUidStateIsBackgroundAndLessImportant) {
-            AtomicReference<Message> delayedMessage = new AtomicReference<>();
-            getPostDelayedMessageArguments(delayedMessage, new AtomicLong());
             mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
-            delayedMessage.get().getCallback().run();
         }
 
-        getLatestPostMessageArgument().getCallback().run();
         verify(cb, atLeastOnce())
                 .onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange));
     }
@@ -781,7 +759,7 @@
     private UidStateChangedCallback addUidStateChangeCallback() {
         UidStateChangedCallback cb =
                 Mockito.mock(UidStateChangedCallback.class);
-        mIntf.addUidStateChangedCallback(mHandler, cb);
+        mIntf.addUidStateChangedCallback(r -> r.run(), cb);
         return cb;
     }
 
@@ -795,30 +773,6 @@
         assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND));
     }
 
-    private void getPostDelayedMessageArguments(AtomicReference<Message> message,
-            AtomicLong delay) {
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-
-        verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture());
-
-        if (message != null) {
-            message.set(messageCaptor.getValue());
-        }
-        if (delay != null) {
-            delay.set(delayCaptor.getValue());
-        }
-    }
-
-    private Message getLatestPostMessageArgument() {
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture());
-
-        return messageCaptor.getValue();
-    }
-
     private UidProcStateUpdateBuilder procStateBuilder(int uid) {
         return new UidProcStateUpdateBuilder(mIntf, uid);
     }
@@ -896,8 +850,14 @@
 
     private static class AppOpsUidStateTrackerTestClock extends Clock {
 
+        private AppOpsUidStateTrackerTestExecutor mExecutor;
         long mElapsedRealTime = 0x5f3759df;
 
+        AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) {
+            mExecutor = executor;
+            executor.setUptime(mElapsedRealTime);
+        }
+
         @Override
         public long elapsedRealtime() {
             return mElapsedRealTime;
@@ -905,6 +865,53 @@
 
         void advanceTime(long time) {
             mElapsedRealTime += time;
+            mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime
+        }
+    }
+
+    private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor {
+
+        private static class QueueElement implements Comparable<QueueElement> {
+
+            private long mExecutionTime;
+            private Runnable mRunnable;
+
+            private QueueElement(long executionTime, Runnable runnable) {
+                mExecutionTime = executionTime;
+                mRunnable = runnable;
+            }
+
+            @Override
+            public int compareTo(QueueElement queueElement) {
+                return Long.compare(mExecutionTime, queueElement.mExecutionTime);
+            }
+        }
+
+        private long mUptime = 0;
+
+        private PriorityQueue<QueueElement> mDelayedMessages = new PriorityQueue();
+
+        @Override
+        public void execute(Runnable runnable) {
+            runnable.run();
+        }
+
+        @Override
+        public void executeDelayed(Runnable runnable, long delay) {
+            if (delay <= 0) {
+                execute(runnable);
+            }
+
+            mDelayedMessages.add(new QueueElement(mUptime + delay, runnable));
+        }
+
+        private void setUptime(long uptime) {
+            while (!mDelayedMessages.isEmpty()
+                    && mDelayedMessages.peek().mExecutionTime <= uptime) {
+                mDelayedMessages.poll().mRunnable.run();
+            }
+
+            mUptime = uptime;
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index 19b798d..b7bbcd75 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,15 +134,36 @@
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getHardSatiatedConsumptionLimit());
+
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
+        assertEquals(0, mEconomicPolicy.getMinSatiatedBalance(0, pkgRestricted));
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
-        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
-                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
+
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
                 mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
+
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+        assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES
+                        + EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, pkgUpdater));
+        // Make sure it doesn't suggest a min balance greater than max.
+        final int updateCount = (int) (EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+                / EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater)))
+                .thenReturn(updateCount);
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+                mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
                 mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
     }
@@ -152,8 +173,10 @@
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
         setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                arcToCake(1));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -163,8 +186,12 @@
         assertEquals(arcToCake(10), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         final String pkgExempted = "com.pkg.exempted";
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
-        assertEquals(arcToCake(9), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
-        assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+        assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3);
+        assertEquals(arcToCake(4) + 3 * arcToCake(1),
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
     }
 
     @Test
@@ -175,6 +202,8 @@
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+                arcToCake(-4));
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
         assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -186,6 +215,10 @@
         when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+        final String pkgUpdater = "com.pkg.updater";
+        when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+        assertEquals(arcToCake(0) + 5 * arcToCake(0),
+                mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
 
         // Test min+max reversed.
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
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;
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 5d9d765..6b8c26d 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -25,7 +25,6 @@
 
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -39,6 +38,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ef203d0..57ded99 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -56,7 +56,6 @@
 import android.graphics.Point;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
 import android.hardware.input.VirtualKeyEvent;
 import android.hardware.input.VirtualMouseButtonEvent;
 import android.hardware.input.VirtualMouseRelativeEvent;
@@ -84,6 +83,7 @@
 
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6..fc2a4cf 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -38,7 +38,6 @@
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -56,7 +55,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class AutomaticBrightnessControllerTest {
     private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 6a6cd6c..800f60b 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -35,7 +35,6 @@
 import android.os.Temperature;
 import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,7 +56,6 @@
 import java.util.List;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class BrightnessThrottlerTest {
     private static final float EPSILON = 0.000001f;
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
index 26a83a2..53d8de0c 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
@@ -24,7 +24,6 @@
 
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -39,7 +38,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class ColorFadeTest {
     private static final int DISPLAY_ID = 123;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad..04702c4 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -27,7 +27,6 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -44,7 +43,6 @@
 import java.nio.file.Path;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class DisplayDeviceConfigTest {
     private DisplayDeviceConfig mDisplayDeviceConfig;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 3eb1dea..3c7bb2a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -53,12 +53,10 @@
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
-import android.hardware.input.InputManagerInternal;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.MessageQueue;
 import android.os.Process;
-import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayEventReceiver;
@@ -78,6 +76,7 @@
 import com.android.server.SystemService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -106,7 +105,6 @@
 import java.util.stream.LongStream;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerServiceTest {
     private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 53fa3e2..a1e5ce7 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -22,14 +22,10 @@
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
 import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
 
-
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-import static com.android.server.display.AutomaticBrightnessController
-                                                      .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
 import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
@@ -51,7 +47,6 @@
 import android.os.Temperature;
 import android.os.Temperature.ThrottlingStatus;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 import android.test.mock.MockContentResolver;
 import android.util.MathUtils;
 
@@ -76,7 +71,6 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class HighBrightnessModeControllerTest {
 
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index cc68ba8..0b33c30 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -45,7 +45,6 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayInfo;
@@ -67,7 +66,6 @@
 import java.util.Set;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public class LogicalDisplayMapperTest {
     private static int sUniqueTestDisplayId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index b0738fd..5a43530 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -26,7 +26,6 @@
 
 import android.app.PropertyInvalidatedCache;
 import android.graphics.Point;
-import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -40,7 +39,6 @@
 import java.io.OutputStream;
 
 @SmallTest
-@Presubmit
 public class LogicalDisplayTest {
     private static final int DISPLAY_ID = 0;
     private static final int LAYER_STACK = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
index 9f1a209..92d8abd 100644
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
@@ -3,18 +3,10 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {
-          "include-filter": "com.android.server.display."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
+        {"include-filter": "com.android.server.display"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
   ]
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index f69c5c2..fabf535 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 
 import android.hardware.display.BrightnessInfo;
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,7 +28,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class BrightnessEventTest {
     private BrightnessEvent mBrightnessEvent;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index ffc2e0d..57aa61a 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -28,7 +27,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class BrightnessReasonTest {
     private BrightnessReason mBrightnessReason;
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index 844f5d4..e390bcc 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,13 +19,14 @@
 import android.content.Context
 import android.content.ContextWrapper
 import android.hardware.display.DisplayViewport
-import android.hardware.input.InputManagerInternal
 import android.os.IInputConstants
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
 import android.view.Display
 import android.view.PointerIcon
 import androidx.test.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -35,7 +36,6 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.never
@@ -43,9 +43,8 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.junit.MockitoJUnit
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
 
 /**
  * Tests for {@link InputManagerService}.
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a4..04ba7d3 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,13 +35,13 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
 
 import androidx.annotation.NonNull;
 
 import com.android.server.LocalServices;
 import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index fe46c14..a76b82b 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -58,7 +58,6 @@
 import android.content.pm.PackageManager;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -76,6 +75,7 @@
 import com.android.internal.accessibility.AccessibilityShortcutController;
 import com.android.server.GestureLauncherService;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e2c94c5..49fd1ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -296,6 +296,14 @@
     }
 
     @Test
+    public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        mContentRecorder.setContentRecordingSession(null);
+        mTask.removeImmediately();
+    }
+
+    @Test
     public void testUpdateMirroredSurface_capturedAreaResized() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 91b868d..be7c112 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,9 +136,6 @@
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
 
-    // TODO(b/226201975): remove once RoleService supports pre-created users
-    private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
     public VoiceInteractionManagerService(Context context) {
         super(context);
         mContext = context;
@@ -309,24 +306,14 @@
             return hotwordDetectionConnection.mIdentity;
         }
 
+        // TODO(b/226201975): remove this method once RoleService supports pre-created users
         @Override
         public void onPreCreatedUserConversion(int userId) {
-            Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
-            for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
-                UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
-                if (preCreatedUser.getIdentifier() == userId) {
-                    Slogf.d(TAG, "Updating role on pre-created user %d", userId);
-                    mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
-                            preCreatedUser);
-                    mIgnoredPreCreatedUsers.remove(i);
-                    return;
-                }
-            }
-            Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
-                    + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+            Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+                    userId);
+            mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+                                                UserHandle.of(userId));
         }
-
     }
 
     // implementation entry point and binder service
@@ -809,8 +796,10 @@
             if (TextUtils.isEmpty(curInteractor)) {
                 return null;
             }
-            if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+            if (DEBUG) {
+                Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
                     + " user=" + userHandle);
+            }
             return ComponentName.unflattenFromString(curInteractor);
         }
 
@@ -818,8 +807,9 @@
             Settings.Secure.putStringForUser(mContext.getContentResolver(),
                     Settings.Secure.VOICE_INTERACTION_SERVICE,
                     comp != null ? comp.flattenToShortString() : "", userHandle);
-            if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
-                    + " user=" + userHandle);
+            if (DEBUG) {
+                Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+            }
         }
 
         ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1917,7 +1907,6 @@
                 pw.println("  mTemporarilyDisabled: " + mTemporarilyDisabled);
                 pw.println("  mCurUser: " + mCurUser);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
-                pw.println("  mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
                 dumpSupportedUsers(pw, "  ");
                 mDbHelper.dump(pw);
                 if (mImpl == null) {
@@ -2031,6 +2020,11 @@
 
                 List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
 
+                if (DEBUG) {
+                    Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+                            roleHolders);
+                }
+
                 // TODO(b/226201975): this method is beling called when a pre-created user is added,
                 // at which point it doesn't have any role holders. But it's not called again when
                 // the actual user is added (i.e., when the  pre-created user is converted), so we
@@ -2041,9 +2035,9 @@
                 if (roleHolders.isEmpty()) {
                     UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
                     if (userInfo != null && userInfo.preCreated) {
-                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
-                                userInfo.toFullString());
-                        mIgnoredPreCreatedUsers.add(user);
+                        Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+                                + " this method will be called again when it's converted to a real"
+                                + " user", userInfo.toFullString());
                         return;
                     }
                 }
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index a415217..bba819c 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -82,8 +82,16 @@
             if (attr1[i].name != attr2[i].name) {
                 return false
             }
-            val v1 = ConstantEvaluator.evaluate(context, attr1[i].value)
-            val v2 = ConstantEvaluator.evaluate(context, attr2[i].value)
+            val value1 = attr1[i].value
+            val value2 = attr2[i].value
+            if (value1 == null && value2 == null) {
+                continue
+            }
+            if (value1 == null || value2 == null) {
+                return false
+            }
+            val v1 = ConstantEvaluator.evaluate(context, value1)
+            val v2 = ConstantEvaluator.evaluate(context, value2)
             if (v1 != v2) {
                 return false
             }