Merge changes I7850ea5b,I9759b7dd

* changes:
  Prepare PackageState/PackageUserState for API
  Add runtime unmodifiable check for PackageState
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index bb57161..4e24909 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -16,6 +16,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
+        <profileable android:shell="true" />
         <activity android:name="android.perftests.utils.PerfTestActivity"
             android:exported="true">
           <intent-filter>
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 3452f58..6d1e6d0 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -28,16 +28,16 @@
 import com.android.internal.util.ConcurrentUtils
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import java.io.File
+import java.io.FileOutputStream
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
 import libcore.io.IoUtils
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import java.io.File
-import java.io.FileOutputStream
-import java.util.concurrent.ArrayBlockingQueue
-import java.util.concurrent.TimeUnit
 
 @LargeTest
 @RunWith(Parameterized::class)
@@ -180,8 +180,8 @@
         protected abstract fun parseImpl(file: File): PackageType
     }
 
-    class ParallelParser1(private val cacher: PackageCacher1? = null)
-        : ParallelParser<PackageParser.Package>(cacher) {
+    class ParallelParser1(private val cacher: PackageCacher1? = null) :
+        ParallelParser<PackageParser.Package>(cacher) {
         val parser = PackageParser().apply {
             setCallback { true }
         }
@@ -189,8 +189,8 @@
         override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
     }
 
-    class ParallelParser2(cacher: PackageCacher2? = null)
-        : ParallelParser<PackageImpl>(cacher) {
+    class ParallelParser2(cacher: PackageCacher2? = null) :
+        ParallelParser<PackageImpl>(cacher) {
         val input = ThreadLocal.withInitial {
             // For testing, just disable enforcement to avoid hooking up to compat framework
             ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
@@ -218,7 +218,7 @@
             })
 
         override fun parseImpl(file: File) =
-                parser.parsePackage(input.get()!!.reset(), file, 0, null).result
+                parser.parsePackage(input.get()!!.reset(), file, 0).result
                         as PackageImpl
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4d5eef2..1e13dbf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1404,8 +1404,31 @@
         }
         mChangedJobList.remove(cancelled);
         // Cancel if running.
-        mConcurrencyManager.stopJobOnServiceContextLocked(
+        final boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked(
                 cancelled, reason, internalReasonCode, debugReason);
+        // If the job was running, the JobServiceContext should log with state FINISHED.
+        if (!wasRunning) {
+            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
+                    cancelled.getSourceUid(), null, cancelled.getBatteryName(),
+                    FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__CANCELLED,
+                    internalReasonCode, cancelled.getStandbyBucket(),
+                    cancelled.getJobId(),
+                    cancelled.hasChargingConstraint(),
+                    cancelled.hasBatteryNotLowConstraint(),
+                    cancelled.hasStorageNotLowConstraint(),
+                    cancelled.hasTimingDelayConstraint(),
+                    cancelled.hasDeadlineConstraint(),
+                    cancelled.hasIdleConstraint(),
+                    cancelled.hasConnectivityConstraint(),
+                    cancelled.hasContentTriggerConstraint(),
+                    cancelled.isRequestedExpeditedJob(),
+                    /* isRunningAsExpeditedJob */ false,
+                    reason,
+                    cancelled.getJob().isPrefetch(),
+                    cancelled.getJob().getPriority(),
+                    cancelled.getEffectivePriority(),
+                    cancelled.getNumFailures());
+        }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
             if (DEBUG) Slog.i(TAG, "Tracking replacement job " + incomingJob.toShortString());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index dfa1442..fcfb45c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -742,6 +742,10 @@
                 }
             } catch (XmlPullParserException | IOException e) {
                 Slog.wtf(TAG, "Error jobstore xml.", e);
+            } catch (Exception e) {
+                // Crashing at this point would result in a boot loop, so live with a general
+                // Exception for system stability's sake.
+                Slog.wtf(TAG, "Unexpected exception", e);
             } finally {
                 if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
                     mPersistInfo.countAllJobsLoaded = numJobs;
@@ -890,6 +894,9 @@
             } catch (IOException e) {
                 Slog.d(TAG, "Error I/O Exception.", e);
                 return null;
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Constraints contained invalid data", e);
+                return null;
             }
 
             parser.next(); // Consume </constraints>
@@ -986,8 +993,14 @@
                 return null;
             }
 
-            PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
-            jobBuilder.setExtras(extras);
+            final PersistableBundle extras;
+            try {
+                extras = PersistableBundle.restoreFromXml(parser);
+                jobBuilder.setExtras(extras);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Persisted extras contained invalid data", e);
+                return null;
+            }
             parser.nextTag(); // Consume </extras>
 
             final JobInfo builtJob;
diff --git a/api/Android.mk b/api/Android.mk
new file mode 100644
index 0000000..ce5f995
--- /dev/null
+++ b/api/Android.mk
@@ -0,0 +1,2 @@
+.PHONY: checkapi
+checkapi: frameworks-base-api-current-compat frameworks-base-api-system-current-compat frameworks-base-api-module-lib-current-compat
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 4efaeea..444f91d 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -222,7 +222,7 @@
         || !Read32(stream, &entry_count)) {
       return nullptr;
     }
-    target_inline_entries.emplace_back(std::make_tuple(target_entry, entry_offset, entry_count));
+    target_inline_entries.emplace_back(target_entry, entry_offset, entry_count);
   }
 
   // Read the inline overlay resource values
@@ -241,7 +241,7 @@
         || !Read32(stream, &value.data_value)) {
       return nullptr;
     }
-    target_values.emplace_back(std::make_pair(config_index, value));
+    target_values.emplace_back(config_index, value);
   }
 
   // Read the configurations
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7fd4283..7dcfab4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10056,7 +10056,7 @@
     method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
     method public static boolean hasIsolatedStorage();
-    method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE) public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
     field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
     field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
     field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 887af1f..99005a4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1168,9 +1168,11 @@
 package android.hardware.devicestate {
 
   public final class DeviceStateManager {
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelBaseStateOverride();
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelStateRequest();
     method @NonNull public int[] getSupportedStates();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
+    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestBaseStateOverride(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
     field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
@@ -1221,6 +1223,7 @@
     method @Nullable public android.view.Display.Mode getGlobalUserPreferredDisplayMode();
     method @NonNull public int[] getUserDisabledHdrTypes();
     method public boolean isMinimalPostProcessingRequested(int);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void overrideHdrTypes(int, @NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setGlobalUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
     method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
@@ -2883,7 +2886,6 @@
     ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
     method @NonNull public static android.os.IBinder getInternalDisplayToken();
     method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
-    method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]);
   }
 
   public class SurfaceControlViewHost {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 80121b7..0e1b47f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1094,7 +1094,7 @@
                 && (options == null
                         || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
             throw new AndroidRuntimeException(
-                    "Calling startActivity() from outside of an Activity "
+                    "Calling startActivity() from outside of an Activity"
                             + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                             + " Is this really what you want?");
         }
@@ -1128,7 +1128,7 @@
     public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
-                    "Calling startActivities() from outside of an Activity "
+                    "Calling startActivities() from outside of an Activity"
                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                     + " Is this really what you want?");
         }
@@ -1142,7 +1142,7 @@
         warnIfCallingFromSystemProcess();
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
-                    "Calling startActivities() from outside of an Activity "
+                    "Calling startActivities() from outside of an Activity"
                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                     + " Is this really what you want?");
         }
diff --git a/core/java/android/app/UidObserver.java b/core/java/android/app/UidObserver.java
new file mode 100644
index 0000000..9e92807
--- /dev/null
+++ b/core/java/android/app/UidObserver.java
@@ -0,0 +1,50 @@
+/*
+ * 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 android.app;
+
+/**
+ * Default implementation of {@link IUidObserver} for which all methods are
+ * no-op. Subclasses can override the select methods they're interested in
+ * handling.
+ *
+ * @hide
+ */
+public class UidObserver extends IUidObserver.Stub {
+    @Override
+    public void onUidActive(int uid) {
+    }
+
+    @Override
+    public void onUidCachedChanged(int uid, boolean cached) {
+    }
+
+    @Override
+    public void onUidGone(int uid, boolean disabled) {
+    }
+
+    @Override
+    public void onUidIdle(int uid, boolean disabled) {
+    }
+
+    @Override
+    public void onUidProcAdjChanged(int uid) {
+    }
+
+    @Override
+    public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
+    }
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 10d6f2d..64fed63 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -78,7 +78,6 @@
     public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
     private static final int PARSE_IS_SYSTEM_DIR = 1 << 4;
     private static final int PARSE_COLLECT_CERTIFICATES = 1 << 5;
-    private static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     private static final String TAG_APPLICATION = "application";
     private static final String TAG_PACKAGE_VERIFIER = "package-verifier";
     private static final String TAG_PROFILEABLE = "profileable";
@@ -103,7 +102,7 @@
     public static ParseResult<PackageLite> parsePackageLite(ParseInput input,
             File packageFile, int flags) {
         if (packageFile.isDirectory()) {
-            return parseClusterPackageLite(input, packageFile, /* frameworkSplits= */ null, flags);
+            return parseClusterPackageLite(input, packageFile, flags);
         } else {
             return parseMonolithicPackageLite(input, packageFile, flags);
         }
@@ -137,38 +136,19 @@
     /**
      * Parse lightweight details about a directory of APKs.
      *
-     * @param packageDirOrApk is the folder that contains split apks for a regular app or the
-     *                        framework-res.apk for framwork-res splits (in which case the
-     *                        splits come in the <code>frameworkSplits</code> parameter)
+     * @param packageDir is the folder that contains split apks for a regular app
      */
     public static ParseResult<PackageLite> parseClusterPackageLite(ParseInput input,
-            File packageDirOrApk, List<File> frameworkSplits, int flags) {
+            File packageDir, int flags) {
         final File[] files;
-        final boolean parsingFrameworkSplits = (flags & PARSE_FRAMEWORK_RES_SPLITS) != 0;
-        if (parsingFrameworkSplits) {
-            if (ArrayUtils.isEmpty(frameworkSplits)) {
-                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                        "No packages found in split");
-            }
-            files = frameworkSplits.toArray(new File[frameworkSplits.size() + 1]);
-            // we also want to process the base apk so add it to the array
-            files[files.length - 1] = packageDirOrApk;
-        } else {
-            files = packageDirOrApk.listFiles();
-            if (ArrayUtils.isEmpty(files)) {
-                return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
-                        "No packages found in split");
-            }
-            // Apk directory is directly nested under the current directory
-            if (files.length == 1 && files[0].isDirectory()) {
-                return parseClusterPackageLite(input, files[0], frameworkSplits, flags);
-            }
+        files = packageDir.listFiles();
+        if (ArrayUtils.isEmpty(files)) {
+            return input.error(PackageManager.INSTALL_PARSE_FAILED_NOT_APK,
+                    "No packages found in split");
         }
-
-        if (parsingFrameworkSplits) {
-            // disable the flag for checking the certificates of the splits. We know they
-            // won't match, but we rely on the mainline apex to be safe if it was installed
-            flags = flags & ~PARSE_COLLECT_CERTIFICATES;
+        // Apk directory is directly nested under the current directory
+        if (files.length == 1 && files[0].isDirectory()) {
+            return parseClusterPackageLite(input, files[0], flags);
         }
 
         String packageName = null;
@@ -186,10 +166,6 @@
                     }
 
                     final ApkLite lite = result.getResult();
-                    if (parsingFrameworkSplits && file == files[files.length - 1]) {
-                        baseApk = lite;
-                        break;
-                    }
                     // Assert that all package names and version codes are
                     // consistent with the first one we encounter.
                     if (packageName == null) {
@@ -201,8 +177,7 @@
                                     "Inconsistent package " + lite.getPackageName() + " in " + file
                                             + "; expected " + packageName);
                         }
-                        // we allow version codes that do not match for framework splits
-                        if (!parsingFrameworkSplits && versionCode != lite.getVersionCode()) {
+                        if (versionCode != lite.getVersionCode()) {
                             return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST,
                                     "Inconsistent version " + lite.getVersionCode() + " in " + file
                                             + "; expected " + versionCode);
@@ -217,15 +192,11 @@
                     }
                 }
             }
-            // baseApk is set in the last iteration of the for each loop when we are parsing
-            // frameworkRes splits or needs to be done now otherwise
-            if (!parsingFrameworkSplits) {
-                baseApk = apks.remove(null);
-            }
+            baseApk = apks.remove(null);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
-        return composePackageLiteFromApks(input, packageDirOrApk, baseApk, apks);
+        return composePackageLiteFromApks(input, packageDir, baseApk, apks);
     }
 
     /**
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 30aa4db..bdd45e6 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware.devicestate;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -115,6 +116,52 @@
     }
 
     /**
+     * Submits a {@link DeviceStateRequest request} to override the base state of the device. This
+     * should only be used for testing, where you want to simulate the physical change to the
+     * device state.
+     * <p>
+     * By default, the request is kept active until one of the following occurs:
+     * <ul>
+     *     <li>The physical state of the device changes</li>
+     *     <li>The system deems the request can no longer be honored, for example if the requested
+     *     state becomes unsupported.
+     *     <li>A call to {@link #cancelBaseStateOverride}.
+     *     <li>Another processes submits a request succeeding this request in which case the request
+     *     will be canceled.
+     * </ul>
+     *
+     * Submitting a base state override request may not cause any change in the presentation
+     * of the system if there is an emulated request made through {@link #requestState}, as the
+     * emulated override requests take priority.
+     *
+     * @throws IllegalArgumentException if the requested state is unsupported.
+     * @throws SecurityException if the caller does not hold the
+     * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+     *
+     * @see DeviceStateRequest
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+            @Nullable @CallbackExecutor Executor executor,
+            @Nullable DeviceStateRequest.Callback callback) {
+        mGlobal.requestBaseStateOverride(request, executor, callback);
+    }
+
+    /**
+     * Cancels the active {@link DeviceStateRequest} previously submitted with a call to
+     * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     * <p>
+     * This method is noop if there is no base state request currently active.
+     *
+     * @throws SecurityException if the caller does not hold the
+     * {@link android.Manifest.permission#CONTROL_DEVICE_STATE} permission.
+     */
+    @RequiresPermission(Manifest.permission.CONTROL_DEVICE_STATE)
+    public void cancelBaseStateOverride() {
+        mGlobal.cancelBaseStateOverride();
+    }
+
+    /**
      * Registers a callback to receive notifications about changes in device state.
      *
      * @param executor the executor to process notifications.
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index aba538f..738045d 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.os.Binder;
@@ -81,6 +82,7 @@
     @VisibleForTesting
     public DeviceStateManagerGlobal(@NonNull IDeviceStateManager deviceStateManager) {
         mDeviceStateManager = deviceStateManager;
+        registerCallbackIfNeededLocked();
     }
 
     /**
@@ -116,27 +118,22 @@
      * DeviceStateRequest.Callback)
      * @see DeviceStateRequest
      */
+    @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+            conditional = true)
     public void requestState(@NonNull DeviceStateRequest request,
             @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
-        if (callback == null && executor != null) {
-            throw new IllegalArgumentException("Callback must be supplied with executor.");
-        } else if (executor == null && callback != null) {
-            throw new IllegalArgumentException("Executor must be supplied with callback.");
-        }
-
+        DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+                executor);
         synchronized (mLock) {
-            registerCallbackIfNeededLocked();
-
             if (findRequestTokenLocked(request) != null) {
                 // This request has already been submitted.
                 return;
             }
-
             // Add the request wrapper to the mRequests array before requesting the state as the
             // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
             // same process as this instance.
             IBinder token = new Binder();
-            mRequests.put(token, new DeviceStateRequestWrapper(request, callback, executor));
+            mRequests.put(token, requestWrapper);
 
             try {
                 mDeviceStateManager.requestState(token, request.getState(), request.getFlags());
@@ -153,10 +150,10 @@
      *
      * @see DeviceStateManager#cancelStateRequest
      */
+    @RequiresPermission(value = android.Manifest.permission.CONTROL_DEVICE_STATE,
+            conditional = true)
     public void cancelStateRequest() {
         synchronized (mLock) {
-            registerCallbackIfNeededLocked();
-
             try {
                 mDeviceStateManager.cancelStateRequest();
             } catch (RemoteException ex) {
@@ -166,6 +163,56 @@
     }
 
     /**
+     * Submits a {@link DeviceStateRequest request} to modify the base state of the device.
+     *
+     * @see DeviceStateManager#requestBaseStateOverride(DeviceStateRequest, Executor,
+     * DeviceStateRequest.Callback)
+     * @see DeviceStateRequest
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void requestBaseStateOverride(@NonNull DeviceStateRequest request,
+            @Nullable Executor executor, @Nullable DeviceStateRequest.Callback callback) {
+        DeviceStateRequestWrapper requestWrapper = new DeviceStateRequestWrapper(request, callback,
+                executor);
+        synchronized (mLock) {
+            if (findRequestTokenLocked(request) != null) {
+                // This request has already been submitted.
+                return;
+            }
+            // Add the request wrapper to the mRequests array before requesting the state as the
+            // callback could be triggered immediately if the mDeviceStateManager IBinder is in the
+            // same process as this instance.
+            IBinder token = new Binder();
+            mRequests.put(token, requestWrapper);
+
+            try {
+                mDeviceStateManager.requestBaseStateOverride(token, request.getState(),
+                        request.getFlags());
+            } catch (RemoteException ex) {
+                mRequests.remove(token);
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Cancels a {@link DeviceStateRequest request} previously submitted with a call to
+     * {@link #requestBaseStateOverride(DeviceStateRequest, Executor, DeviceStateRequest.Callback)}.
+     *
+     * @see DeviceStateManager#cancelBaseStateOverride
+     */
+    @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)
+    public void cancelBaseStateOverride() {
+        synchronized (mLock) {
+            try {
+                mDeviceStateManager.cancelBaseStateOverride();
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
      * Registers a callback to receive notifications about changes in device state.
      *
      * @see DeviceStateManager#registerCallback(Executor, DeviceStateCallback)
@@ -179,9 +226,6 @@
                 // This callback is already registered.
                 return;
             }
-
-            registerCallbackIfNeededLocked();
-
             // Add the callback wrapper to the mCallbacks array after registering the callback as
             // the callback could be triggered immediately if the mDeviceStateManager IBinder is in
             // the same process as this instance.
@@ -357,6 +401,8 @@
 
         DeviceStateRequestWrapper(@NonNull DeviceStateRequest request,
                 @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+            validateRequestWrapperParameters(callback, executor);
+
             mRequest = request;
             mCallback = callback;
             mExecutor = executor;
@@ -377,5 +423,14 @@
 
             mExecutor.execute(() -> mCallback.onRequestCanceled(mRequest));
         }
+
+        private void validateRequestWrapperParameters(
+                @Nullable DeviceStateRequest.Callback callback, @Nullable Executor executor) {
+            if (callback == null && executor != null) {
+                throw new IllegalArgumentException("Callback must be supplied with executor.");
+            } else if (executor == null && callback != null) {
+                throw new IllegalArgumentException("Executor must be supplied with callback.");
+            }
+        }
     }
 }
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index e450e42..7175eae 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -41,6 +41,10 @@
      * previously registered with {@link #registerCallback(IDeviceStateManagerCallback)} before a
      * call to this method.
      *
+     * Requesting a state does not cancel a base state override made through
+     * {@link #requestBaseStateOverride}, but will still attempt to put the device into the
+     * supplied {@code state}.
+     *
      * @param token the request token provided
      * @param state the state of device the request is asking for
      * @param flags any flags that correspond to the request
@@ -50,14 +54,53 @@
      * @throws IllegalStateException if the supplied {@code token} has already been registered.
      * @throws IllegalArgumentException if the supplied {@code state} is not supported.
      */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
     void requestState(IBinder token, int state, int flags);
 
     /**
      * Cancels the active request previously submitted with a call to
-     * {@link #requestState(IBinder, int, int)}.
+     * {@link #requestState(IBinder, int, int)}. Will have no effect on any base state override that
+     * was previously requested with {@link #requestBaseStateOverride}.
      *
      * @throws IllegalStateException if a callback has not yet been registered for the calling
      *         process.
      */
+    @JavaPassthrough(annotation=
+            "@android.annotation.RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true)")
     void cancelStateRequest();
+
+    /**
+     * Requests that the device's base state be overridden to the supplied {@code state}. A callback
+     * <b>MUST</b> have been previously registered with
+     * {@link #registerCallback(IDeviceStateManagerCallback)} before a call to this method.
+     *
+     * This method should only be used for testing, when you want to simulate the device physically
+     * changing states. If you are looking to change device state for a feature, where the system
+     * should still be aware that the physical state is different than the emulated state, use
+     * {@link #requestState}.
+     *
+     * @param token the request token provided
+     * @param state the state of device the request is asking for
+     * @param flags any flags that correspond to the request
+     *
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
+     * @throws IllegalStateException if the supplied {@code token} has already been registered.
+     * @throws IllegalArgumentException if the supplied {@code state} is not supported.
+     */
+    @JavaPassthrough(annotation=
+        "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+    void requestBaseStateOverride(IBinder token, int state, int flags);
+
+    /**
+     * Cancels the active base state request previously submitted with a call to
+     * {@link #overrideBaseState(IBinder, int, int)}.
+     *
+     * @throws IllegalStateException if a callback has not yet been registered for the calling
+     *         process.
+     */
+    @JavaPassthrough(annotation=
+        "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+    void cancelBaseStateOverride();
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8bc11cb..dfb4236 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -855,6 +855,16 @@
         return mGlobal.getUserDisabledHdrTypes();
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    @TestApi
+    public void overrideHdrTypes(int displayId, @NonNull int[] modes) {
+        mGlobal.overrideHdrTypes(displayId, modes);
+    }
 
     /**
      * Creates a virtual display.
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index e2f8592..9294dea 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -20,9 +20,11 @@
 import static android.hardware.display.DisplayManager.EventsMask;
 import static android.view.Display.HdrCapabilities.HdrType;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -577,6 +579,20 @@
         }
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     *
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    public void overrideHdrTypes(int displayId, int[] modes) {
+        try {
+            mDm.overrideHdrTypes(displayId, modes);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+
     public void requestColorMode(int displayId, int colorMode) {
         try {
             mDm.requestColorMode(displayId, colorMode);
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index d57a272..00bccc6 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -28,6 +28,7 @@
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.window.DisplayWindowPolicyController;
 import android.window.ScreenCapture;
@@ -398,6 +399,11 @@
     public abstract DisplayWindowPolicyController getDisplayWindowPolicyController(int displayId);
 
     /**
+     * Get DisplayPrimaries from SF for a particular display.
+     */
+    public abstract SurfaceControl.DisplayPrimaries getDisplayNativePrimaries(int displayId);
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index fa3c450..b166e21 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -83,6 +83,9 @@
     // No permissions required.
     int[] getUserDisabledHdrTypes();
 
+    // Requires ACCESS_SURFACE_FLINGER permission.
+    void overrideHdrTypes(int displayId, in int[] modes);
+
     // Requires CONFIGURE_DISPLAY_COLOR_MODE
     void requestColorMode(int displayId, int colorMode);
 
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index aa5480a..4a18333 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -16,12 +16,13 @@
 
 package android.hardware.radio;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -29,28 +30,36 @@
 /**
  * Implements the RadioTuner interface by forwarding calls to radio service.
  */
-class TunerAdapter extends RadioTuner {
+final class TunerAdapter extends RadioTuner {
     private static final String TAG = "BroadcastRadio.TunerAdapter";
 
-    @NonNull private final ITuner mTuner;
-    @NonNull private final TunerCallbackAdapter mCallback;
-    private boolean mIsClosed = false;
+    private final ITuner mTuner;
+    private final TunerCallbackAdapter mCallback;
+    private final Object mLock = new Object();
 
-    private @RadioManager.Band int mBand;
+    @GuardedBy("mLock")
+    private boolean mIsClosed;
 
+    @GuardedBy("mLock")
+    @RadioManager.Band
+    private int mBand;
+
+    @GuardedBy("mLock")
     private ProgramList mLegacyListProxy;
+
+    @GuardedBy("mLock")
     private Map<String, String> mLegacyListFilter;
 
-    TunerAdapter(@NonNull ITuner tuner, @NonNull TunerCallbackAdapter callback,
+    TunerAdapter(ITuner tuner, TunerCallbackAdapter callback,
             @RadioManager.Band int band) {
-        mTuner = Objects.requireNonNull(tuner);
-        mCallback = Objects.requireNonNull(callback);
+        mTuner = Objects.requireNonNull(tuner, "Tuner cannot be null");
+        mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
         mBand = band;
     }
 
     @Override
     public void close() {
-        synchronized (mTuner) {
+        synchronized (mLock) {
             if (mIsClosed) {
                 Log.v(TAG, "Tuner is already closed");
                 return;
@@ -60,8 +69,8 @@
                 mLegacyListProxy.close();
                 mLegacyListProxy = null;
             }
-            mCallback.close();
         }
+        mCallback.close();
         try {
             mTuner.close();
         } catch (RemoteException e) {
@@ -71,16 +80,20 @@
 
     @Override
     public int setConfiguration(RadioManager.BandConfig config) {
-        if (config == null) return RadioManager.STATUS_BAD_VALUE;
+        if (config == null) {
+            return RadioManager.STATUS_BAD_VALUE;
+        }
         try {
             mTuner.setConfiguration(config);
-            mBand = config.getType();
+            synchronized (mLock) {
+                mBand = config.getType();
+            }
             return RadioManager.STATUS_OK;
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Can't set configuration", e);
             return RadioManager.STATUS_BAD_VALUE;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
     }
@@ -94,7 +107,7 @@
             config[0] = mTuner.getConfiguration();
             return RadioManager.STATUS_OK;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
     }
@@ -107,7 +120,7 @@
             Log.e(TAG, "Can't set muted", e);
             return RadioManager.STATUS_ERROR;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -118,7 +131,7 @@
         try {
             return mTuner.isMuted();
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return true;
         }
     }
@@ -126,12 +139,13 @@
     @Override
     public int step(int direction, boolean skipSubChannel) {
         try {
-            mTuner.step(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+            mTuner.step(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't step", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -140,12 +154,13 @@
     @Override
     public int scan(int direction, boolean skipSubChannel) {
         try {
-            mTuner.scan(direction == RadioTuner.DIRECTION_DOWN, skipSubChannel);
+            mTuner.scan(/* directionDown= */ direction == RadioTuner.DIRECTION_DOWN,
+                    skipSubChannel);
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't scan", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -154,7 +169,11 @@
     @Override
     public int tune(int channel, int subChannel) {
         try {
-            mTuner.tune(ProgramSelector.createAmFmSelector(mBand, channel, subChannel));
+            int band;
+            synchronized (mLock) {
+                band = mBand;
+            }
+            mTuner.tune(ProgramSelector.createAmFmSelector(band, channel, subChannel));
         } catch (IllegalStateException e) {
             Log.e(TAG, "Can't tune", e);
             return RadioManager.STATUS_INVALID_OPERATION;
@@ -162,18 +181,18 @@
             Log.e(TAG, "Can't tune", e);
             return RadioManager.STATUS_BAD_VALUE;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
     }
 
     @Override
-    public void tune(@NonNull ProgramSelector selector) {
+    public void tune(ProgramSelector selector) {
         try {
             mTuner.tune(selector);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -185,7 +204,7 @@
             Log.e(TAG, "Can't cancel", e);
             return RadioManager.STATUS_INVALID_OPERATION;
         } catch (RemoteException e) {
-            Log.e(TAG, "service died", e);
+            Log.e(TAG, "Service died", e);
             return RadioManager.STATUS_DEAD_OBJECT;
         }
         return RadioManager.STATUS_OK;
@@ -196,7 +215,7 @@
         try {
             mTuner.cancelAnnouncement();
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -217,11 +236,12 @@
     }
 
     @Override
-    public @Nullable Bitmap getMetadataImage(int id) {
+    @Nullable
+    public Bitmap getMetadataImage(int id) {
         try {
             return mTuner.getImage(id);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -230,66 +250,72 @@
         try {
             return mTuner.startBackgroundScan();
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull List<RadioManager.ProgramInfo>
+    public List<RadioManager.ProgramInfo>
             getProgramList(@Nullable Map<String, String> vendorFilter) {
-        synchronized (mTuner) {
+        synchronized (mLock) {
             if (mLegacyListProxy == null || !Objects.equals(mLegacyListFilter, vendorFilter)) {
                 Log.i(TAG, "Program list filter has changed, requesting new list");
                 mLegacyListProxy = new ProgramList();
                 mLegacyListFilter = vendorFilter;
-
                 mCallback.clearLastCompleteList();
-                mCallback.setProgramListObserver(mLegacyListProxy, () -> { });
-                try {
-                    mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
-                } catch (RemoteException ex) {
-                    throw new RuntimeException("service died", ex);
-                }
+                mCallback.setProgramListObserver(mLegacyListProxy, () -> {
+                    Log.i(TAG, "Empty closeListener in programListObserver");
+                });
             }
-
-            List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
-            if (list == null) throw new IllegalStateException("Program list is not ready yet");
-            return list;
         }
+        try {
+            mTuner.startProgramListUpdates(new ProgramList.Filter(vendorFilter));
+        } catch (RemoteException ex) {
+            throw new RuntimeException("Service died", ex);
+        }
+
+        List<RadioManager.ProgramInfo> list = mCallback.getLastCompleteList();
+        if (list == null) {
+            throw new IllegalStateException("Program list is not ready yet");
+        }
+        return list;
     }
 
     @Override
-    public @Nullable ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
-        synchronized (mTuner) {
+    @Nullable
+    public ProgramList getDynamicProgramList(@Nullable ProgramList.Filter filter) {
+        synchronized (mLock) {
             if (mLegacyListProxy != null) {
                 mLegacyListProxy.close();
                 mLegacyListProxy = null;
             }
             mLegacyListFilter = null;
-
-            ProgramList list = new ProgramList();
-            mCallback.setProgramListObserver(list, () -> {
-                try {
-                    mTuner.stopProgramListUpdates();
-                } catch (IllegalStateException ex) {
-                    // it's fine to not stop updates if tuner is already closed
-                } catch (RemoteException ex) {
-                    Log.e(TAG, "Couldn't stop program list updates", ex);
-                }
-            });
-
-            try {
-                mTuner.startProgramListUpdates(filter);
-            } catch (UnsupportedOperationException ex) {
-                Log.i(TAG, "Program list is not supported with this hardware");
-                return null;
-            } catch (RemoteException ex) {
-                mCallback.setProgramListObserver(null, () -> { });
-                throw new RuntimeException("service died", ex);
-            }
-
-            return list;
         }
+        ProgramList list = new ProgramList();
+        mCallback.setProgramListObserver(list, () -> {
+            try {
+                mTuner.stopProgramListUpdates();
+            } catch (IllegalStateException ex) {
+                // it's fine to not stop updates if tuner is already closed
+                Log.e(TAG, "Tuner may already be closed", ex);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Couldn't stop program list updates", ex);
+            }
+        });
+
+        try {
+            mTuner.startProgramListUpdates(filter);
+        } catch (UnsupportedOperationException ex) {
+            Log.i(TAG, "Program list is not supported with this hardware");
+            return null;
+        } catch (RemoteException ex) {
+            mCallback.setProgramListObserver(null, () -> {
+                Log.i(TAG, "Empty closeListener in programListObserver");
+            });
+            throw new RuntimeException("Service died", ex);
+        }
+
+        return list;
     }
 
     @Override
@@ -315,7 +341,7 @@
         try {
             return mTuner.isConfigFlagSupported(flag);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -324,7 +350,7 @@
         try {
             return mTuner.isConfigFlagSet(flag);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
@@ -333,25 +359,26 @@
         try {
             mTuner.setConfigFlag(flag, value);
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
+    public Map<String, String> setParameters(Map<String, String> parameters) {
         try {
-            return mTuner.setParameters(Objects.requireNonNull(parameters));
+            return mTuner.setParameters(Objects.requireNonNull(parameters,
+                    "Parameters cannot be null"));
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
     @Override
-    public @NonNull Map<String, String> getParameters(@NonNull List<String> keys) {
+    public Map<String, String> getParameters(List<String> keys) {
         try {
-            return mTuner.getParameters(Objects.requireNonNull(keys));
+            return mTuner.getParameters(Objects.requireNonNull(keys, "Keys cannot be null"));
         } catch (RemoteException e) {
-            throw new RuntimeException("service died", e);
+            throw new RuntimeException("Service died", e);
         }
     }
 
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index e3f7001..b9782a8 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -16,12 +16,13 @@
 
 package android.hardware.radio;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -29,23 +30,31 @@
 /**
  * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback.
  */
-class TunerCallbackAdapter extends ITunerCallback.Stub {
+final class TunerCallbackAdapter extends ITunerCallback.Stub {
     private static final String TAG = "BroadcastRadio.TunerCallbackAdapter";
 
     private final Object mLock = new Object();
-    @NonNull private final RadioTuner.Callback mCallback;
-    @NonNull private final Handler mHandler;
+    private final RadioTuner.Callback mCallback;
+    private final Handler mHandler;
 
+    @GuardedBy("mLock")
     @Nullable ProgramList mProgramList;
 
     // cache for deprecated methods
+    @GuardedBy("mLock")
     boolean mIsAntennaConnected = true;
+
+    @GuardedBy("mLock")
     @Nullable List<RadioManager.ProgramInfo> mLastCompleteList;
-    private boolean mDelayedCompleteCallback = false;
+
+    @GuardedBy("mLock")
+    private boolean mDelayedCompleteCallback;
+
+    @GuardedBy("mLock")
     @Nullable RadioManager.ProgramInfo mCurrentProgramInfo;
 
-    TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
-        mCallback = callback;
+    TunerCallbackAdapter(RadioTuner.Callback callback, @Nullable Handler handler) {
+        mCallback = Objects.requireNonNull(callback, "Callback cannot be null");
         if (handler == null) {
             mHandler = new Handler(Looper.getMainLooper());
         } else {
@@ -55,31 +64,39 @@
 
     void close() {
         synchronized (mLock) {
-            if (mProgramList != null) mProgramList.close();
+            if (mProgramList != null) {
+                mProgramList.close();
+            }
         }
     }
 
     void setProgramListObserver(@Nullable ProgramList programList,
-            @NonNull ProgramList.OnCloseListener closeListener) {
-        Objects.requireNonNull(closeListener);
+            ProgramList.OnCloseListener closeListener) {
+        Objects.requireNonNull(closeListener, "CloseListener cannot be null");
         synchronized (mLock) {
             if (mProgramList != null) {
                 Log.w(TAG, "Previous program list observer wasn't properly closed, closing it...");
                 mProgramList.close();
             }
             mProgramList = programList;
-            if (programList == null) return;
+            if (programList == null) {
+                return;
+            }
             programList.setOnCloseListener(() -> {
                 synchronized (mLock) {
-                    if (mProgramList != programList) return;
+                    if (mProgramList != programList) {
+                        return;
+                    }
                     mProgramList = null;
                     mLastCompleteList = null;
-                    closeListener.onClose();
                 }
+                closeListener.onClose();
             });
             programList.addOnCompleteListener(() -> {
                 synchronized (mLock) {
-                    if (mProgramList != programList) return;
+                    if (mProgramList != programList) {
+                        return;
+                    }
                     mLastCompleteList = programList.toList();
                     if (mDelayedCompleteCallback) {
                         Log.d(TAG, "Sending delayed onBackgroundScanComplete callback");
@@ -109,7 +126,11 @@
     }
 
     boolean isAntennaConnected() {
-        return mIsAntennaConnected;
+        boolean isConnected;
+        synchronized (mLock) {
+            isConnected = mIsAntennaConnected;
+        }
+        return isConnected;
     }
 
     @Override
@@ -177,7 +198,9 @@
 
     @Override
     public void onAntennaState(boolean connected) {
-        mIsAntennaConnected = connected;
+        synchronized (mLock) {
+            mIsAntennaConnected = connected;
+        }
         mHandler.post(() -> mCallback.onAntennaState(connected));
     }
 
@@ -186,6 +209,7 @@
         mHandler.post(() -> mCallback.onBackgroundScanAvailabilityChange(isAvailable));
     }
 
+    @GuardedBy("mLock")
     private void sendBackgroundScanCompleteLocked() {
         mDelayedCompleteCallback = false;
         mHandler.post(() -> mCallback.onBackgroundScanComplete());
@@ -213,8 +237,10 @@
     public void onProgramListUpdated(ProgramList.Chunk chunk) {
         mHandler.post(() -> {
             synchronized (mLock) {
-                if (mProgramList == null) return;
-                mProgramList.apply(Objects.requireNonNull(chunk));
+                if (mProgramList == null) {
+                    return;
+                }
+                mProgramList.apply(Objects.requireNonNull(chunk, "Chunk cannot be null"));
             }
         });
     }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7436601..9ed55ff 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -967,7 +967,7 @@
                 Log.d(TAG, "Input should have started before starting Stylus handwriting.");
                 return;
             }
-            maybeCreateInkWindow();
+            maybeCreateAndInitInkWindow();
             if (!mOnPreparedStylusHwCalled) {
                 // prepare hasn't been called by Stylus HOVER.
                 onPrepareStylusHandwriting();
@@ -1027,8 +1027,7 @@
          */
         @Override
         public void initInkWindow() {
-            maybeCreateInkWindow();
-            mInkWindow.initOnly();
+            maybeCreateAndInitInkWindow();
             onPrepareStylusHandwriting();
             mOnPreparedStylusHwCalled = true;
         }
@@ -1036,11 +1035,13 @@
         /**
          * Create and attach token to Ink window if it wasn't already created.
          */
-        private void maybeCreateInkWindow() {
+        private void maybeCreateAndInitInkWindow() {
             if (mInkWindow == null) {
                 mInkWindow = new InkWindow(mWindow.getContext());
                 mInkWindow.setToken(mToken);
             }
+            mInkWindow.initOnly();
+            setInkViewVisibilityListener();
             // TODO(b/243571274): set an idle-timeout after which InkWindow is removed.
         }
 
@@ -2469,13 +2470,19 @@
      * @param motionEvent {@link MotionEvent} from stylus.
      */
     public void onStylusHandwritingMotionEvent(@NonNull MotionEvent motionEvent) {
-        if (mInkWindow.isInkViewVisible()) {
+        if (mInkWindow != null && mInkWindow.isInkViewVisible()) {
             mInkWindow.getDecorView().dispatchTouchEvent(motionEvent);
         } else {
             if (mPendingEvents == null) {
                 mPendingEvents = new RingBuffer(MotionEvent.class, MAX_EVENTS_BUFFER);
             }
             mPendingEvents.append(motionEvent);
+            setInkViewVisibilityListener();
+        }
+    }
+
+    private void setInkViewVisibilityListener() {
+        if (mInkWindow != null) {
             mInkWindow.setInkViewVisibilityListener(() -> {
                 if (mPendingEvents != null && !mPendingEvents.isEmpty()) {
                     for (MotionEvent event : mPendingEvents.toArray()) {
@@ -2539,6 +2546,7 @@
             mHandler.removeCallbacks(mFinishHwRunnable);
         }
         mFinishHwRunnable = null;
+        mPendingEvents.clear();
 
         final int requestId = mHandwritingRequestId.getAsInt();
         mHandwritingRequestId = OptionalInt.empty();
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 88649cb..933769a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -76,7 +76,7 @@
     String getUserAccount(int userId);
     void setUserAccount(int userId, String accountName);
     long getUserCreationTime(int userId);
-    boolean isUserSwitcherEnabled(int mUserId);
+    boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
     int getUserSerialNumber(int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1e91700..5c809a1 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5265,36 +5265,10 @@
     public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) {
 
         try {
-            if (!mService.isUserSwitcherEnabled(mUserId)) {
-                return false;
-            }
+            return mService.isUserSwitcherEnabled(showEvenIfNotActionable, mUserId);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
-
-        // The feature is enabled. But is it worth showing?
-        return showEvenIfNotActionable
-                || areThereUsersToWhichToSwitch() // There are switchable users.
-                || !hasUserRestrictionForUser(DISALLOW_ADD_USER, mUserId); // New users can be added
-    }
-
-    /** Returns whether there are any users (other than the current user) to which to switch. */
-    @RequiresPermission(anyOf = {
-            android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.CREATE_USERS
-    })
-    private boolean areThereUsersToWhichToSwitch() {
-        final List<UserInfo> users = getAliveUsers();
-        if (users == null) {
-            return false;
-        }
-        int switchableUserCount = 0;
-        for (UserInfo user : users) {
-            if (user.supportsSwitchToByUser()) {
-                ++switchableUserCount;
-            }
-        }
-        return switchableUserCount > 1;
     }
 
     /**
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 08de87e..c1606e8 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2549,7 +2549,7 @@
      * called on first creation of a new file on external storage, and whenever the
      * media type of the file is updated later.
      *
-     * This API doesn't require any special permissions, though typical implementations
+     * This API requires MANAGE_EXTERNAL_STORAGE permission and typical implementations
      * will require being called from an SELinux domain that allows setting file attributes
      * related to quota (eg the GID or project ID).
      *
@@ -2568,11 +2568,16 @@
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE)
     public void updateExternalStorageFileQuotaType(@NonNull File path,
             @QuotaType int quotaType) throws IOException {
         long projectId;
         final String filePath = path.getCanonicalPath();
-        final StorageVolume volume = getStorageVolume(path);
+        // MANAGE_EXTERNAL_STORAGE permission is required as FLAG_INCLUDE_SHARED_PROFILE is being
+        // set while querying getVolumeList.
+        final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(),
+                FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE | FLAG_INCLUDE_SHARED_PROFILE);
+        final StorageVolume volume = getStorageVolume(availableVolumes, path);
         if (volume == null) {
             Log.w(TAG, "Failed to update quota type for " + filePath);
             return;
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
index 7edc987..fe53d4e 100644
--- a/core/java/android/preference/GenericInflater.java
+++ b/core/java/android/preference/GenericInflater.java
@@ -406,7 +406,7 @@
             InflateException ie = new InflateException(attrs
                     .getPositionDescription()
                     + ": Error inflating class "
-                    + constructor.getClass().getName());
+                    + constructor.getDeclaringClass().getName());
             ie.initCause(e);
             throw ie;
         }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3a9af7e..cab6acb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7462,6 +7462,13 @@
                 "trust_agents_initialized";
 
         /**
+         * Set to 1 by the system after the list of known trust agents have been initialized.
+         * @hide
+         */
+        public static final String KNOWN_TRUST_AGENTS_INITIALIZED =
+                "known_trust_agents_initialized";
+
+        /**
          * The Logging ID (a unique 64-bit value) as a hex string.
          * Used as a pseudonymous identifier for logging.
          * @deprecated This identifier is poorly initialized and has
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 154fcab..913efbd 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4820,5 +4820,12 @@
          * @hide
          */
         public static final String COLUMN_USAGE_SETTING = "usage_setting";
+
+        /**
+         * TelephonyProvider column name for user handle associated with this sim.
+         *
+         * @hide
+         */
+        public static final String COLUMN_USER_HANDLE = "user_handle";
     }
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 067946e..b2a26fa 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -67,7 +67,6 @@
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
 
 /**
  * System private interface to the window manager.
@@ -969,11 +968,4 @@
      * treatment.
      */
     boolean isLetterboxBackgroundMultiColored();
-
-    /**
-     * Captures the entire display specified by the displayId using the args provided. If the args
-     * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
-     */
-    oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
-            in ScreenCapture.ScreenCaptureListener listener);
 }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index fb0cac3..2f7ae49 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -225,8 +225,6 @@
     private static native void nativeSetDimmingEnabled(long transactionObj, long nativeObject,
             boolean dimmingEnabled);
 
-    private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
-
     private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
             InputWindowHandle handle);
 
@@ -449,6 +447,7 @@
     private String mName;
 
      /**
+     * Note: do not rename, this field is used by native code.
      * @hide
      */
     public long mNativeObject;
@@ -2037,18 +2036,6 @@
     }
 
     /**
-     * Overrides HDR modes for a display device.
-     *
-     * If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
-     * Exception.
-     * @hide
-     */
-    @TestApi
-    public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
-        nativeOverrideHdrTypes(displayToken, modes);
-    }
-
-    /**
      * @hide
      */
     public static long[] getPhysicalDisplayIds() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6f775d..ec1f73f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -286,7 +286,7 @@
      * @hide
      */
     public static final boolean LOCAL_LAYOUT =
-            SystemProperties.getBoolean("persist.debug.local_layout", false);
+            SystemProperties.getBoolean("persist.debug.local_layout", true);
 
     /**
      * Set this system property to true to force the view hierarchy to render
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index b8f39a9..887d027 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,16 +24,11 @@
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
 import android.util.Log;
 import android.view.SurfaceControl;
 
-import libcore.util.NativeAllocationRegistry;
-
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
 
 /**
  * Handles display and layer captures for the system.
@@ -44,23 +39,18 @@
     private static final String TAG = "ScreenCapture";
 
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
-            long captureListener);
+            ScreenCaptureListener captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
-            long captureListener);
-    private static native long nativeCreateScreenCaptureListener(
-            Consumer<ScreenshotHardwareBuffer> consumer);
-    private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
-    private static native long nativeReadListenerFromParcel(Parcel in);
-    private static native long getNativeListenerFinalizer();
+            ScreenCaptureListener captureListener);
 
     /**
-     * @param captureArgs     Arguments about how to take the screenshot
+     * @param captureArgs Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
+        return nativeCaptureDisplay(captureArgs, captureListener);
     }
 
     /**
@@ -71,8 +61,10 @@
      */
     public static ScreenshotHardwareBuffer captureDisplay(
             DisplayCaptureArgs captureArgs) {
-        SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
-        int status = captureDisplay(captureArgs, screenCaptureListener.getScreenCaptureListener());
+        SyncScreenCaptureListener
+                screenCaptureListener = new SyncScreenCaptureListener();
+
+        int status = captureDisplay(captureArgs, screenCaptureListener);
         if (status != 0) {
             return null;
         }
@@ -83,13 +75,14 @@
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer      The root layer to capture.
-     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
-     *                   Rect()' or null if no cropping is desired. If the root layer does not
-     *                   have a buffer or a crop set, then a non-empty source crop must be
-     *                   specified.
-     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
-     *                   up/down.
+     * @param layer            The root layer to capture.
+     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
+     *                         Rect()' or null if no cropping is desired. If the root layer does not
+     *                         have a buffer or a crop set, then a non-empty source crop must be
+     *                         specified.
+     * @param frameScale       The desired scale of the returned buffer; the raw
+     *                         screen will be scaled up/down.
+     *
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -101,14 +94,15 @@
     /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
-     * @param layer      The root layer to capture.
-     * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
-     *                   Rect()' or null if no cropping is desired. If the root layer does not
-     *                   have a buffer or a crop set, then a non-empty source crop must be
-     *                   specified.
-     * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
-     *                   up/down.
-     * @param format     The desired pixel format of the returned buffer.
+     * @param layer            The root layer to capture.
+     * @param sourceCrop       The portion of the root surface to capture; caller may pass in 'new
+     *                         Rect()' or null if no cropping is desired. If the root layer does not
+     *                         have a buffer or a crop set, then a non-empty source crop must be
+     *                         specified.
+     * @param frameScale       The desired scale of the returned buffer; the raw
+     *                         screen will be scaled up/down.
+     * @param format           The desired pixel format of the returned buffer.
+     *
      * @return Returns a HardwareBuffer that contains the layer capture.
      * @hide
      */
@@ -130,7 +124,7 @@
             LayerCaptureArgs captureArgs) {
         SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
 
-        int status = captureLayers(captureArgs, screenCaptureListener.getScreenCaptureListener());
+        int status = captureLayers(captureArgs, screenCaptureListener);
         if (status != 0) {
             return null;
         }
@@ -141,7 +135,6 @@
     /**
      * Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
      * handles to exclude.
-     *
      * @hide
      */
     public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -157,13 +150,24 @@
     }
 
     /**
-     * @param captureArgs     Arguments about how to take the screenshot
+     * @param captureArgs Arguments about how to take the screenshot
      * @param captureListener A listener to receive the screenshot callback
      * @hide
      */
     public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
             @NonNull ScreenCaptureListener captureListener) {
-        return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
+        return nativeCaptureLayers(captureArgs, captureListener);
+    }
+
+    /**
+     * @hide
+     */
+    public interface ScreenCaptureListener {
+        /**
+         * The callback invoked when the screen capture is complete.
+         * @param hardwareBuffer Data containing info about the screen capture.
+         */
+        void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
     }
 
     /**
@@ -186,16 +190,15 @@
             mContainsHdrLayers = containsHdrLayers;
         }
 
-        /**
-         * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
-         *
-         * @param hardwareBuffer       The existing HardwareBuffer object
-         * @param namedColorSpace      Integer value of a named color space {@link ColorSpace.Named}
-         * @param containsSecureLayers Indicates whether this graphic buffer contains captured
-         *                             contents of secure layers, in which case the screenshot
-         *                             should not be persisted.
-         * @param containsHdrLayers    Indicates whether this graphic buffer contains HDR content.
-         */
+       /**
+        * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+        * @param hardwareBuffer The existing HardwareBuffer object
+        * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
+        * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+        *                             contents of secure layers, in which case the screenshot
+        *                             should not be persisted.
+        * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
+        */
         private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
                 int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
             ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -217,7 +220,6 @@
         public boolean containsSecureLayers() {
             return mContainsSecureLayers;
         }
-
         /**
          * Returns whether the screenshot contains at least one HDR layer.
          * This information may be useful for informing the display whether this screenshot
@@ -232,7 +234,7 @@
          * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
          * into
          * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
-         * <p>
+         *
          * CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
          * directly
          * use the {@link HardwareBuffer} directly.
@@ -248,23 +250,44 @@
         }
     }
 
+    private static class SyncScreenCaptureListener implements ScreenCaptureListener {
+        private static final int SCREENSHOT_WAIT_TIME_S = 1;
+        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+
+        @Override
+        public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
+            mScreenshotHardwareBuffer = hardwareBuffer;
+            mCountDownLatch.countDown();
+        }
+
+        private ScreenshotHardwareBuffer waitForScreenshot() {
+            try {
+                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to wait for screen capture result", e);
+            }
+
+            return mScreenshotHardwareBuffer;
+        }
+    }
+
     /**
      * A common arguments class used for various screenshot requests. This contains arguments that
      * are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
-     *
      * @hide
      */
-    public static class CaptureArgs implements Parcelable {
-        public final int mPixelFormat;
-        public final Rect mSourceCrop = new Rect();
-        public final float mFrameScaleX;
-        public final float mFrameScaleY;
-        public final boolean mCaptureSecureLayers;
-        public final boolean mAllowProtected;
-        public final long mUid;
-        public final boolean mGrayscale;
+    private abstract static class CaptureArgs {
+        private final int mPixelFormat;
+        private final Rect mSourceCrop = new Rect();
+        private final float mFrameScaleX;
+        private final float mFrameScaleY;
+        private final boolean mCaptureSecureLayers;
+        private final boolean mAllowProtected;
+        private final long mUid;
+        private final boolean mGrayscale;
 
-        private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
+        private CaptureArgs(Builder<? extends Builder<?>> builder) {
             mPixelFormat = builder.mPixelFormat;
             mSourceCrop.set(builder.mSourceCrop);
             mFrameScaleX = builder.mFrameScaleX;
@@ -275,23 +298,12 @@
             mGrayscale = builder.mGrayscale;
         }
 
-        private CaptureArgs(Parcel in) {
-            mPixelFormat = in.readInt();
-            mSourceCrop.readFromParcel(in);
-            mFrameScaleX = in.readFloat();
-            mFrameScaleY = in.readFloat();
-            mCaptureSecureLayers = in.readBoolean();
-            mAllowProtected = in.readBoolean();
-            mUid = in.readLong();
-            mGrayscale = in.readBoolean();
-        }
-
         /**
          * The Builder class used to construct {@link CaptureArgs}
          *
-         * @param <T> A builder that extends {@link CaptureArgs.Builder}
+         * @param <T> A builder that extends {@link Builder}
          */
-        public static class Builder<T extends CaptureArgs.Builder<T>> {
+        abstract static class Builder<T extends Builder<T>> {
             private int mPixelFormat = PixelFormat.RGBA_8888;
             private final Rect mSourceCrop = new Rect();
             private float mFrameScaleX = 1;
@@ -302,14 +314,6 @@
             private boolean mGrayscale;
 
             /**
-             * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
-             * valid.
-             */
-            public CaptureArgs build() {
-                return new CaptureArgs(this);
-            }
-
-            /**
              * The desired pixel format of the returned buffer.
              */
             public T setPixelFormat(int pixelFormat) {
@@ -391,47 +395,15 @@
             /**
              * Each sub class should return itself to allow the builder to chain properly
              */
-            T getThis() {
-                return (T) this;
-            }
+            abstract T getThis();
         }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(@NonNull Parcel dest, int flags) {
-            dest.writeInt(mPixelFormat);
-            mSourceCrop.writeToParcel(dest, flags);
-            dest.writeFloat(mFrameScaleX);
-            dest.writeFloat(mFrameScaleY);
-            dest.writeBoolean(mCaptureSecureLayers);
-            dest.writeBoolean(mAllowProtected);
-            dest.writeLong(mUid);
-            dest.writeBoolean(mGrayscale);
-        }
-
-        public static final Parcelable.Creator<CaptureArgs> CREATOR =
-                new Parcelable.Creator<CaptureArgs>() {
-                    @Override
-                    public CaptureArgs createFromParcel(Parcel in) {
-                        return new CaptureArgs(in);
-                    }
-
-                    @Override
-                    public CaptureArgs[] newArray(int size) {
-                        return new CaptureArgs[size];
-                    }
-                };
     }
 
     /**
      * The arguments class used to make display capture requests.
      *
+     * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
      * @hide
-     * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
      */
     public static class DisplayCaptureArgs extends CaptureArgs {
         private final IBinder mDisplayToken;
@@ -516,8 +488,8 @@
     /**
      * The arguments class used to make layer capture requests.
      *
+     * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
      * @hide
-     * @see #nativeCaptureLayers(LayerCaptureArgs, long)
      */
     public static class LayerCaptureArgs extends CaptureArgs {
         private final long mNativeLayer;
@@ -558,17 +530,6 @@
                 return new LayerCaptureArgs(this);
             }
 
-            public Builder(SurfaceControl layer, CaptureArgs args) {
-                setLayer(layer);
-                setPixelFormat(args.mPixelFormat);
-                setSourceCrop(args.mSourceCrop);
-                setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
-                setCaptureSecureLayers(args.mCaptureSecureLayers);
-                setAllowProtected(args.mAllowProtected);
-                setUid(args.mUid);
-                setGrayscale(args.mGrayscale);
-            }
-
             public Builder(SurfaceControl layer) {
                 setLayer(layer);
             }
@@ -581,6 +542,7 @@
                 return this;
             }
 
+
             /**
              * An array of layer handles to exclude.
              */
@@ -602,106 +564,8 @@
             Builder getThis() {
                 return this;
             }
+
         }
     }
 
-    /**
-     * The object used to receive the results when invoking screen capture requests via
-     * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
-     * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
-     */
-    public static class ScreenCaptureListener implements Parcelable {
-        private final long mNativeObject;
-        private static final NativeAllocationRegistry sRegistry =
-                NativeAllocationRegistry.createMalloced(
-                        ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
-
-        /**
-         * @param consumer The callback invoked when the screen capture is complete.
-         */
-        public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
-            mNativeObject = nativeCreateScreenCaptureListener(consumer);
-            sRegistry.registerNativeAllocation(this, mNativeObject);
-        }
-
-        private ScreenCaptureListener(Parcel in) {
-            if (in.readBoolean()) {
-                mNativeObject = nativeReadListenerFromParcel(in);
-                sRegistry.registerNativeAllocation(this, mNativeObject);
-            } else {
-                mNativeObject = 0;
-            }
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(@NonNull Parcel dest, int flags) {
-            if (mNativeObject == 0) {
-                dest.writeBoolean(false);
-            } else {
-                dest.writeBoolean(true);
-                nativeWriteListenerToParcel(mNativeObject, dest);
-            }
-        }
-
-        public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
-                new Parcelable.Creator<ScreenCaptureListener>() {
-                    @Override
-                    public ScreenCaptureListener createFromParcel(Parcel in) {
-                        return new ScreenCaptureListener(in);
-                    }
-
-                    @Override
-                    public ScreenCaptureListener[] newArray(int size) {
-                        return new ScreenCaptureListener[0];
-                    }
-                };
-    }
-
-    /**
-     * A helper class to handle the async screencapture callbacks synchronously. This should only
-     * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
-     */
-    public static class SyncScreenCaptureListener {
-        private static final int SCREENSHOT_WAIT_TIME_S = 1;
-        private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
-        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-        private final ScreenCaptureListener mScreenCaptureListener;
-
-        public SyncScreenCaptureListener() {
-            mScreenCaptureListener = new ScreenCaptureListener(screenshotHardwareBuffer -> {
-                mScreenshotHardwareBuffer = screenshotHardwareBuffer;
-                mCountDownLatch.countDown();
-            });
-        }
-
-        /**
-         * @return The underlying {@link ScreenCaptureListener}
-         */
-        public ScreenCaptureListener getScreenCaptureListener() {
-            return mScreenCaptureListener;
-        }
-
-        /**
-         * Waits until the screenshot callback has been invoked and the screenshot is ready. This
-         * can return {@code null} if the screenshot callback wasn't invoked after
-         * {@link #SCREENSHOT_WAIT_TIME_S} or the screencapture request resulted in an error
-         *
-         * @return A ScreenshotHardwareBuffer for the content that was captured.
-         */
-        @Nullable
-        public ScreenshotHardwareBuffer waitForScreenshot() {
-            try {
-                mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to wait for screen capture result", e);
-            }
-
-            return mScreenshotHardwareBuffer;
-        }
-    }
 }
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index 56e9107..e2c8a31 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -188,6 +188,10 @@
     /**
      * Returns {@code true} if the parameters that are important for task fragment organizers are
      * equal between this {@link TaskFragmentInfo} and {@param that}.
+     * Note that this method is usually called with
+     * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+     * Configuration, Configuration)} to determine if this {@link TaskFragmentInfo} should
+     * be dispatched to the client.
      */
     public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentInfo that) {
         if (that == null) {
diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/TaskFragmentParentInfo.aidl
similarity index 78%
rename from core/java/android/window/ScreenCapture.aidl
rename to core/java/android/window/TaskFragmentParentInfo.aidl
index 267a7c6..79d2209 100644
--- a/core/java/android/window/ScreenCapture.aidl
+++ b/core/java/android/window/TaskFragmentParentInfo.aidl
@@ -16,11 +16,8 @@
 
 package android.window;
 
-/** @hide */
-parcelable ScreenCapture.CaptureArgs;
-
-/** @hide */
-parcelable ScreenCapture.ScreenshotHardwareBuffer;
-
-/** @hide */
-parcelable ScreenCapture.ScreenCaptureListener;
\ No newline at end of file
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+parcelable TaskFragmentParentInfo;
\ No newline at end of file
diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java
new file mode 100644
index 0000000..64b2638
--- /dev/null
+++ b/core/java/android/window/TaskFragmentParentInfo.java
@@ -0,0 +1,158 @@
+/*
+ * 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 android.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.res.Configuration;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The information about the parent Task of a particular TaskFragment
+ * @hide
+ */
+public class TaskFragmentParentInfo implements Parcelable {
+    @NonNull
+    private final Configuration mConfiguration = new Configuration();
+
+    private final int mDisplayId;
+
+    private final boolean mVisibleRequested;
+
+    public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId,
+            boolean visibleRequested) {
+        mConfiguration.setTo(configuration);
+        mDisplayId = displayId;
+        mVisibleRequested = visibleRequested;
+    }
+
+    public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+        mConfiguration.setTo(info.getConfiguration());
+        mDisplayId = info.mDisplayId;
+        mVisibleRequested = info.mVisibleRequested;
+    }
+
+    /** The {@link Configuration} of the parent Task */
+    @NonNull
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    /**
+     * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the
+     * Task is detached from previously associated display.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /** Whether the parent Task is requested to be visible or not */
+    public boolean isVisibleRequested() {
+        return mVisibleRequested;
+    }
+
+    /**
+     * Returns {@code true} if the parameters which are important for task fragment
+     * organizers are equal between this {@link TaskFragmentParentInfo} and {@code that}.
+     * Note that this method is usually called with
+     * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer(
+     * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should
+     * be dispatched to the client.
+     */
+    public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) {
+        if (that == null) {
+            return false;
+        }
+        return getWindowingMode() == that.getWindowingMode() && mDisplayId == that.mDisplayId
+                && mVisibleRequested == that.mVisibleRequested;
+    }
+
+    @WindowConfiguration.WindowingMode
+    private int getWindowingMode() {
+        return mConfiguration.windowConfiguration.getWindowingMode();
+    }
+
+    @Override
+    public String toString() {
+        return TaskFragmentParentInfo.class.getSimpleName() + ":{"
+                + "config=" + mConfiguration
+                + ", displayId=" + mDisplayId
+                + ", visibleRequested=" + mVisibleRequested
+                + "}";
+    }
+
+    /**
+     * Indicates that whether this {@link TaskFragmentParentInfo} equals to {@code obj}.
+     * Note that {@link #equalsForTaskFragmentOrganizer(TaskFragmentParentInfo)} should be used
+     * for most cases because not all {@link Configuration} properties are interested for
+     * {@link TaskFragmentOrganizer}.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof TaskFragmentParentInfo)) {
+            return false;
+        }
+        final TaskFragmentParentInfo that = (TaskFragmentParentInfo) obj;
+        return mConfiguration.equals(that.mConfiguration)
+                && mDisplayId == that.mDisplayId
+                && mVisibleRequested == that.mVisibleRequested;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mConfiguration.hashCode();
+        result = 31 * result + mDisplayId;
+        result = 31 * result + (mVisibleRequested ? 1 : 0);
+        return result;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mConfiguration.writeToParcel(dest, flags);
+        dest.writeInt(mDisplayId);
+        dest.writeBoolean(mVisibleRequested);
+    }
+
+    private TaskFragmentParentInfo(Parcel in) {
+        mConfiguration.readFromParcel(in);
+        mDisplayId = in.readInt();
+        mVisibleRequested = in.readBoolean();
+    }
+
+    public static final Creator<TaskFragmentParentInfo> CREATOR =
+            new Creator<TaskFragmentParentInfo>() {
+                @Override
+                public TaskFragmentParentInfo createFromParcel(Parcel in) {
+                    return new TaskFragmentParentInfo(in);
+                }
+
+                @Override
+                public TaskFragmentParentInfo[] newArray(int size) {
+                    return new TaskFragmentParentInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 04fcd3a..7667743 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -173,10 +173,6 @@
         /** @see #setTaskId(int) */
         private int mTaskId;
 
-        /** @see #setTaskConfiguration(Configuration) */
-        @Nullable
-        private Configuration mTaskConfiguration;
-
         /** @see #setErrorCallbackToken(IBinder) */
         @Nullable
         private IBinder mErrorCallbackToken;
@@ -193,6 +189,9 @@
         @Nullable
         private IBinder mActivityToken;
 
+        @Nullable
+        private TaskFragmentParentInfo mTaskFragmentParentInfo;
+
         public Change(@ChangeType int type) {
             mType = type;
         }
@@ -202,11 +201,11 @@
             mTaskFragmentToken = in.readStrongBinder();
             mTaskFragmentInfo = in.readTypedObject(TaskFragmentInfo.CREATOR);
             mTaskId = in.readInt();
-            mTaskConfiguration = in.readTypedObject(Configuration.CREATOR);
             mErrorCallbackToken = in.readStrongBinder();
             mErrorBundle = in.readBundle(TaskFragmentTransaction.class.getClassLoader());
             mActivityIntent = in.readTypedObject(Intent.CREATOR);
             mActivityToken = in.readStrongBinder();
+            mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR);
         }
 
         @Override
@@ -215,11 +214,11 @@
             dest.writeStrongBinder(mTaskFragmentToken);
             dest.writeTypedObject(mTaskFragmentInfo, flags);
             dest.writeInt(mTaskId);
-            dest.writeTypedObject(mTaskConfiguration, flags);
             dest.writeStrongBinder(mErrorCallbackToken);
             dest.writeBundle(mErrorBundle);
             dest.writeTypedObject(mActivityIntent, flags);
             dest.writeStrongBinder(mActivityToken);
+            dest.writeTypedObject(mTaskFragmentParentInfo, flags);
         }
 
         /** The change is related to the TaskFragment created with this unique token. */
@@ -243,10 +242,10 @@
             return this;
         }
 
+        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
         /** Configuration of the parent Task. */
         @NonNull
         public Change setTaskConfiguration(@NonNull Configuration configuration) {
-            mTaskConfiguration = requireNonNull(configuration);
             return this;
         }
 
@@ -294,6 +293,19 @@
             return this;
         }
 
+        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+        /**
+         * Sets info of the parent Task of the embedded TaskFragment.
+         * @see TaskFragmentParentInfo
+         *
+         * @hide pending unhide
+         */
+        @NonNull
+        public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+            mTaskFragmentParentInfo = requireNonNull(info);
+            return this;
+        }
+
         @ChangeType
         public int getType() {
             return mType;
@@ -313,9 +325,10 @@
             return mTaskId;
         }
 
+        // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release.
         @Nullable
         public Configuration getTaskConfiguration() {
-            return mTaskConfiguration;
+            return mTaskFragmentParentInfo.getConfiguration();
         }
 
         @Nullable
@@ -339,6 +352,13 @@
             return mActivityToken;
         }
 
+        // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release.
+        /** @hide pending unhide */
+        @Nullable
+        public TaskFragmentParentInfo getTaskFragmentParentInfo() {
+            return mTaskFragmentParentInfo;
+        }
+
         @Override
         public String toString() {
             return "Change{ type=" + mType + " }";
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 33ea2e4..641d1a1 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -125,8 +125,15 @@
     /** The container attaches work profile thumbnail for cross profile animation. */
     public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13;
 
+    /**
+     * Whether the window is covered by an app starting window. This is different from
+     * {@link #FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT} which is only set on the Activity window
+     * that contains the starting window.
+     */
+    public static final int FLAG_IS_BEHIND_STARTING_WINDOW = 1 << 14;
+
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
-    public static final int FLAG_FIRST_CUSTOM = 1 << 14;
+    public static final int FLAG_FIRST_CUSTOM = 1 << 15;
 
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
@@ -145,6 +152,7 @@
             FLAG_WILL_IME_SHOWN,
             FLAG_CROSS_PROFILE_OWNER_THUMBNAIL,
             FLAG_CROSS_PROFILE_WORK_THUMBNAIL,
+            FLAG_IS_BEHIND_STARTING_WINDOW,
             FLAG_FIRST_CUSTOM
     })
     public @interface ChangeFlags {}
@@ -351,6 +359,9 @@
         if ((flags & FLAG_FILLS_TASK) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK");
         }
+        if ((flags & FLAG_IS_BEHIND_STARTING_WINDOW) != 0) {
+            sb.append(sb.length() == 0 ? "" : "|").append("IS_BEHIND_STARTING_WINDOW");
+        }
         if ((flags & FLAG_FIRST_CUSTOM) != 0) {
             sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
         }
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index 354eb62..be0b729 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -51,6 +51,7 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -69,6 +70,7 @@
 
     private static final SynchronizedPool<SimpleIconFactory> sPool =
             new SynchronizedPool<>(Runtime.getRuntime().availableProcessors());
+    private static boolean sPoolEnabled = true;
 
     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
     private static final float BLUR_FACTOR = 1.5f / 48;
@@ -92,7 +94,7 @@
      */
     @Deprecated
     public static SimpleIconFactory obtain(Context ctx) {
-        SimpleIconFactory instance = sPool.acquire();
+        SimpleIconFactory instance = sPoolEnabled ? sPool.acquire() : null;
         if (instance == null) {
             final ActivityManager am = (ActivityManager) ctx.getSystemService(ACTIVITY_SERVICE);
             final int iconDpi = (am == null) ? 0 : am.getLauncherLargeIconDensity();
@@ -106,6 +108,17 @@
         return instance;
     }
 
+    /**
+     * Enables or disables SimpleIconFactory objects pooling. It is enabled in production, you
+     * could use this method in tests and disable the pooling to make the icon rendering more
+     * deterministic because some sizing parameters will not be cached. Please ensure that you
+     * reset this value back after finishing the test.
+     */
+    @VisibleForTesting
+    public static void setPoolEnabled(boolean poolEnabled) {
+        sPoolEnabled = poolEnabled;
+    }
+
     private static int getAttrDimFromContext(Context ctx, @AttrRes int attrId, String errorMsg) {
         final Resources res = ctx.getResources();
         TypedValue outVal = new TypedValue();
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index b1e7d15..deafd19 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -1001,16 +1001,24 @@
     }
 
     /**
+     * This will enable jdwp by default for all apps. It is OK to cache this property
+     * because we expect to reboot the system whenever this property changes
+     */
+    private static final boolean ENABLE_JDWP = SystemProperties.get(
+                          "persist.debug.dalvik.vm.jdwp.enabled").equals("1");
+
+    /**
      * Applies debugger system properties to the zygote arguments.
      *
-     * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
-     * the debugger state is specified via the "--enable-jdwp" flag
-     * in the spawn request.
+     * For eng builds all apps are debuggable. On userdebug and user builds
+     * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are
+     * debuggable. Otherwise, the debugger state is specified via the
+     * "--enable-jdwp" flag in the spawn request.
      *
      * @param args non-null; zygote spawner args
      */
     static void applyDebuggerSystemProperty(ZygoteArguments args) {
-        if (RoSystemProperties.DEBUGGABLE) {
+        if (Build.IS_ENG || ENABLE_JDWP) {
             args.mRuntimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
         }
     }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3e0a6cb..65c2d00 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -164,6 +164,7 @@
     private static final String LOCK_SCREEN_DEVICE_OWNER_INFO = "lockscreen.device_owner_info";
 
     private static final String ENABLED_TRUST_AGENTS = "lockscreen.enabledtrustagents";
+    private static final String KNOWN_TRUST_AGENTS = "lockscreen.knowntrustagents";
     private static final String IS_TRUST_USUALLY_MANAGED = "lockscreen.istrustusuallymanaged";
 
     public static final String CURRENT_LSKF_BASED_PROTECTOR_ID_KEY = "sp-handle";
@@ -1089,31 +1090,50 @@
         return getString(LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, userId) != null;
     }
 
+    /** Updates the list of enabled trust agent in LockSettings storage for the given user. */
     public void setEnabledTrustAgents(Collection<ComponentName> activeTrustAgents, int userId) {
+        setString(ENABLED_TRUST_AGENTS, serializeTrustAgents(activeTrustAgents), userId);
+        getTrustManager().reportEnabledTrustAgentsChanged(userId);
+    }
+
+    /** Returns the list of enabled trust agent in LockSettings storage for the given user. */
+    public List<ComponentName> getEnabledTrustAgents(int userId) {
+        return deserializeTrustAgents(getString(ENABLED_TRUST_AGENTS, userId));
+    }
+
+    /** Updates the list of known trust agent in LockSettings storage for the given user. */
+    public void setKnownTrustAgents(Collection<ComponentName> knownTrustAgents, int userId) {
+        setString(KNOWN_TRUST_AGENTS, serializeTrustAgents(knownTrustAgents), userId);
+    }
+
+    /** Returns the list of known trust agent in LockSettings storage for the given user. */
+    public List<ComponentName> getKnownTrustAgents(int userId) {
+        return deserializeTrustAgents(getString(KNOWN_TRUST_AGENTS, userId));
+    }
+
+    private String serializeTrustAgents(Collection<ComponentName> trustAgents) {
         StringBuilder sb = new StringBuilder();
-        for (ComponentName cn : activeTrustAgents) {
+        for (ComponentName cn : trustAgents) {
             if (sb.length() > 0) {
                 sb.append(',');
             }
             sb.append(cn.flattenToShortString());
         }
-        setString(ENABLED_TRUST_AGENTS, sb.toString(), userId);
-        getTrustManager().reportEnabledTrustAgentsChanged(userId);
+        return sb.toString();
     }
 
-    public List<ComponentName> getEnabledTrustAgents(int userId) {
-        String serialized = getString(ENABLED_TRUST_AGENTS, userId);
-        if (TextUtils.isEmpty(serialized)) {
-            return new ArrayList<ComponentName>();
+    private List<ComponentName> deserializeTrustAgents(String serializedTrustAgents) {
+        if (TextUtils.isEmpty(serializedTrustAgents)) {
+            return new ArrayList<>();
         }
-        String[] split = serialized.split(",");
-        ArrayList<ComponentName> activeTrustAgents = new ArrayList<ComponentName>(split.length);
+        String[] split = serializedTrustAgents.split(",");
+        ArrayList<ComponentName> trustAgents = new ArrayList<>(split.length);
         for (String s : split) {
             if (!TextUtils.isEmpty(s)) {
-                activeTrustAgents.add(ComponentName.unflattenFromString(s));
+                trustAgents.add(ComponentName.unflattenFromString(s));
             }
         }
-        return activeTrustAgents;
+        return trustAgents;
     }
 
     /**
@@ -1487,7 +1507,8 @@
                         STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
                         STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
                         STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
-                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
+                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT,
+                        SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StrongAuthFlags {}
 
@@ -1540,6 +1561,12 @@
         public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
 
         /**
+         * Some authentication is required because the trustagent either timed out or was disabled
+         * manually.
+         */
+        public static final int SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED = 0x100;
+
+        /**
          * Strong auth flags that do not prevent biometric methods from being accepted as auth.
          * If any other flags are set, biometric authentication is disabled.
          */
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index f7a98d1..30d9ea1 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -51,6 +51,21 @@
 
     virtual int handleEvent(int fd, int events, void* data);
 
+    /**
+     * A simple proxy that holds a weak reference to a looper callback.
+     */
+    class WeakLooperCallback : public LooperCallback {
+    protected:
+        virtual ~WeakLooperCallback();
+
+    public:
+        WeakLooperCallback(const wp<LooperCallback>& callback);
+        virtual int handleEvent(int fd, int events, void* data);
+
+    private:
+        wp<LooperCallback> mCallback;
+    };
+
 private:
     JNIEnv* mPollEnv;
     jobject mPollObj;
@@ -131,7 +146,8 @@
         if (events & CALLBACK_EVENT_OUTPUT) {
             looperEvents |= Looper::EVENT_OUTPUT;
         }
-        mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
+        mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents,
+                sp<WeakLooperCallback>::make(this),
                 reinterpret_cast<void*>(events));
     } else {
         mLooper->removeFd(fd);
@@ -162,6 +178,24 @@
 }
 
 
+// --- NativeMessageQueue::WeakLooperCallback ---
+
+NativeMessageQueue::WeakLooperCallback::WeakLooperCallback(const wp<LooperCallback>& callback) :
+        mCallback(callback) {
+}
+
+NativeMessageQueue::WeakLooperCallback::~WeakLooperCallback() {
+}
+
+int NativeMessageQueue::WeakLooperCallback::handleEvent(int fd, int events, void* data) {
+    sp<LooperCallback> callback = mCallback.promote();
+    if (callback != nullptr) {
+        return callback->handleEvent(fd, events, data);
+    }
+    return 0;
+}
+
+
 // ----------------------------------------------------------------------------
 
 sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 8c23b21..764338f 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -39,6 +39,7 @@
 #include "androidfw/AssetManager2.h"
 #include "androidfw/AttributeResolution.h"
 #include "androidfw/MutexGuard.h"
+#include <androidfw/ResourceTimer.h>
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/ResourceUtils.h"
 
@@ -630,6 +631,7 @@
                                    jshort density, jobject typed_value,
                                    jboolean resolve_references) {
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  ResourceTimer _tag(ResourceTimer::Counter::GetResourceValue);
   auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
                                          static_cast<uint16_t>(density));
   if (!value.has_value()) {
@@ -1232,6 +1234,7 @@
   }
 
   ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  ResourceTimer _tag(ResourceTimer::Counter::RetrieveAttributes);
   ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
   auto result =
           RetrieveAttributes(assetmanager.get(), xml_parser, reinterpret_cast<uint32_t*>(attrs),
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index aefec6c..b11f22a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -28,6 +28,7 @@
 #include <android_runtime/android_graphics_GraphicBuffer.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
 #include <android_runtime/android_view_Surface.h>
+#include <android_runtime/android_view_SurfaceControl.h>
 #include <android_runtime/android_view_SurfaceSession.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
@@ -221,8 +222,14 @@
 
 static struct {
     jclass clazz;
+    jfieldID mNativeObject;
+} gTransactionClassInfo;
+
+static struct {
+    jclass clazz;
+    jfieldID mNativeObject;
     jmethodID invokeReleaseCallback;
-} gInvokeReleaseCallback;
+} gSurfaceControlClassInfo;
 
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
@@ -511,10 +518,11 @@
         if (fenceCopy) {
             fenceCopy->incStrong(0);
         }
-        globalCallbackRef->env()->CallStaticVoidMethod(gInvokeReleaseCallback.clazz,
-                                                       gInvokeReleaseCallback.invokeReleaseCallback,
-                                                       globalCallbackRef->object(),
-                                                       reinterpret_cast<jlong>(fenceCopy));
+        globalCallbackRef->env()
+                ->CallStaticVoidMethod(gSurfaceControlClassInfo.clazz,
+                                       gSurfaceControlClassInfo.invokeReleaseCallback,
+                                       globalCallbackRef->object(),
+                                       reinterpret_cast<jlong>(fenceCopy));
     };
 }
 
@@ -1520,27 +1528,6 @@
     transaction->reparent(ctrl, newParent);
 }
 
-static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
-                                   jintArray jHdrTypes) {
-    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
-    if (token == nullptr || jHdrTypes == nullptr) return;
-
-    int* hdrTypes = env->GetIntArrayElements(jHdrTypes, 0);
-    int numHdrTypes = env->GetArrayLength(jHdrTypes);
-
-    std::vector<ui::Hdr> hdrTypesVector;
-    for (int i = 0; i < numHdrTypes; i++) {
-        hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
-    }
-    env->ReleaseIntArrayElements(jHdrTypes, hdrTypes, 0);
-
-    status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
-    if (error != NO_ERROR) {
-        jniThrowExceptionFmt(env, "java/lang/SecurityException",
-                             "ACCESS_SURFACE_FLINGER is missing");
-    }
-}
-
 static jboolean nativeGetBootDisplayModeSupport(JNIEnv* env, jclass clazz) {
     bool isBootDisplayModeSupported = false;
     SurfaceComposerClient::getBootDisplayModeSupport(&isBootDisplayModeSupported);
@@ -1906,6 +1893,28 @@
 
 // ----------------------------------------------------------------------------
 
+SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
+                                                                    jobject surfaceControlObj) {
+    if (!!surfaceControlObj &&
+        env->IsInstanceOf(surfaceControlObj, gSurfaceControlClassInfo.clazz)) {
+        return reinterpret_cast<SurfaceControl*>(
+                env->GetLongField(surfaceControlObj, gSurfaceControlClassInfo.mNativeObject));
+    } else {
+        return nullptr;
+    }
+}
+
+SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSurfaceTransaction(
+        JNIEnv* env, jobject surfaceTransactionObj) {
+    if (!!surfaceTransactionObj &&
+        env->IsInstanceOf(surfaceTransactionObj, gTransactionClassInfo.clazz)) {
+        return reinterpret_cast<SurfaceComposerClient::Transaction*>(
+                env->GetLongField(surfaceTransactionObj, gTransactionClassInfo.mNativeObject));
+    } else {
+        return nullptr;
+    }
+}
+
 static const JNINativeMethod sSurfaceControlMethods[] = {
         // clang-format off
     {"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
@@ -2026,8 +2035,6 @@
             (void*)nativeSetGameContentType },
     {"nativeGetCompositionDataspaces", "()[I",
             (void*)nativeGetCompositionDataspaces},
-    {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
-                (void*)nativeOverrideHdrTypes },
     {"nativeClearContentFrameStats", "(J)Z",
             (void*)nativeClearContentFrameStats },
     {"nativeGetContentFrameStats", "(JLandroid/view/WindowContentFrameStats;)Z",
@@ -2306,11 +2313,18 @@
             GetFieldIDOrDie(env, displayDecorationSupportClazz, "alphaInterpretation", "I");
 
     jclass surfaceControlClazz = FindClassOrDie(env, "android/view/SurfaceControl");
-    gInvokeReleaseCallback.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz);
-    gInvokeReleaseCallback.invokeReleaseCallback =
+    gSurfaceControlClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceControlClazz);
+    gSurfaceControlClassInfo.mNativeObject =
+            GetFieldIDOrDie(env, gSurfaceControlClassInfo.clazz, "mNativeObject", "J");
+    gSurfaceControlClassInfo.invokeReleaseCallback =
             GetStaticMethodIDOrDie(env, surfaceControlClazz, "invokeReleaseCallback",
                                    "(Ljava/util/function/Consumer;J)V");
 
+    jclass surfaceTransactionClazz = FindClassOrDie(env, "android/view/SurfaceControl$Transaction");
+    gTransactionClassInfo.clazz = MakeGlobalRefOrDie(env, surfaceTransactionClazz);
+    gTransactionClassInfo.mNativeObject =
+            GetFieldIDOrDie(env, gTransactionClassInfo.clazz, "mNativeObject", "J");
+
     return err;
 }
 
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index c1929c6..3bada15 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -61,8 +61,9 @@
 } gLayerCaptureArgsClassInfo;
 
 static struct {
-    jmethodID accept;
-} gConsumerClassInfo;
+    jclass clazz;
+    jmethodID onScreenCaptureComplete;
+} gScreenCaptureListenerClassInfo;
 
 static struct {
     jclass clazz;
@@ -97,14 +98,14 @@
 public:
     explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
         env->GetJavaVM(&mVm);
-        mConsumerObject = env->NewGlobalRef(jobject);
-        LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
+        mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
+        LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
     }
 
     ~ScreenCaptureListenerWrapper() {
-        if (mConsumerObject) {
-            getenv()->DeleteGlobalRef(mConsumerObject);
-            mConsumerObject = nullptr;
+        if (mScreenCaptureListenerObject) {
+            getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
+            mScreenCaptureListenerObject = nullptr;
         }
     }
 
@@ -112,8 +113,9 @@
             const gui::ScreenCaptureResults& captureResults) override {
         JNIEnv* env = getenv();
         if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
-            env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
-            checkAndClearException(env, "accept");
+            env->CallVoidMethod(mScreenCaptureListenerObject,
+                                gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
+            checkAndClearException(env, "onScreenCaptureComplete");
             return binder::Status::ok();
         }
         captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -128,15 +130,17 @@
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
-        env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
-        checkAndClearException(env, "accept");
+        env->CallVoidMethod(mScreenCaptureListenerObject,
+                            gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
+                            screenshotHardwareBuffer);
+        checkAndClearException(env, "onScreenCaptureComplete");
         env->DeleteLocalRef(jhardwareBuffer);
         env->DeleteLocalRef(screenshotHardwareBuffer);
         return binder::Status::ok();
     }
 
 private:
-    jobject mConsumerObject;
+    jobject mScreenCaptureListenerObject;
     JavaVM* mVm;
 
     JNIEnv* getenv() {
@@ -190,7 +194,7 @@
 }
 
 static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
-                                 jlong screenCaptureListenerObject) {
+                                 jobject screenCaptureListenerObject) {
     const DisplayCaptureArgs captureArgs =
             displayCaptureArgsFromObject(env, displayCaptureArgsObject);
 
@@ -198,13 +202,13 @@
         return BAD_VALUE;
     }
 
-    sp<gui::IScreenCaptureListener> captureListener =
-            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
+    sp<IScreenCaptureListener> captureListener =
+            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
     return ScreenshotClient::captureDisplay(captureArgs, captureListener);
 }
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
-                                jlong screenCaptureListenerObject) {
+                                jobject screenCaptureListenerObject) {
     LayerCaptureArgs captureArgs;
     getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
     SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -234,70 +238,21 @@
         }
     }
 
-    sp<gui::IScreenCaptureListener> captureListener =
-            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
+    sp<IScreenCaptureListener> captureListener =
+            sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
     return ScreenshotClient::captureLayers(captureArgs, captureListener);
 }
 
-static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
-    sp<gui::IScreenCaptureListener> listener =
-            sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
-    listener->incStrong((void*)nativeCreateScreenCaptureListener);
-    return reinterpret_cast<jlong>(listener.get());
-}
-
-static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
-                                        jobject parcelObj) {
-    Parcel* parcel = parcelForJavaObject(env, parcelObj);
-    if (parcel == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-    ScreenCaptureListenerWrapper* const self =
-            reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
-    if (self != nullptr) {
-        parcel->writeStrongBinder(IInterface::asBinder(self));
-    }
-}
-
-static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
-    Parcel* parcel = parcelForJavaObject(env, parcelObj);
-    if (parcel == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
-    }
-    sp<gui::IScreenCaptureListener> listener =
-            interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
-    if (listener == nullptr) {
-        return 0;
-    }
-    listener->incStrong((void*)nativeCreateScreenCaptureListener);
-    return reinterpret_cast<jlong>(listener.get());
-}
-
-void destroyNativeListener(void* ptr) {
-    ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
-    listener->decStrong((void*)nativeCreateScreenCaptureListener);
-}
-
-static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
-    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
-}
-
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sScreenCaptureMethods[] = {
         // clang-format off
-    {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
+   {"nativeCaptureDisplay",
+            "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
             (void*)nativeCaptureDisplay },
-    {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
+    {"nativeCaptureLayers",
+            "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
             (void*)nativeCaptureLayers },
-    {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
-            (void*)nativeCreateScreenCaptureListener },
-    {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
-    {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
-            (void*)nativeReadListenerFromParcel },
-    {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
         // clang-format on
 };
 
@@ -338,8 +293,12 @@
     gLayerCaptureArgsClassInfo.childrenOnly =
             GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
 
-    jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
-    gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+    jclass screenCaptureListenerClazz =
+            FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
+    gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
+    gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
+            GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
+                             "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
 
     jclass screenshotGraphicsBufferClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/jni/include/android_runtime/android_view_SurfaceControl.h b/core/jni/include/android_runtime/android_view_SurfaceControl.h
new file mode 100644
index 0000000..10a7549
--- /dev/null
+++ b/core/jni/include/android_runtime/android_view_SurfaceControl.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef _ANDROID_VIEW_SURFACECONTROL_H
+#define _ANDROID_VIEW_SURFACECONTROL_H
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
+
+#include "jni.h"
+
+namespace android {
+
+/* Gets the underlying native SurfaceControl for a java SurfaceControl. */
+extern SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(
+        JNIEnv* env, jobject surfaceControlObj);
+
+/* Gets the underlying native SurfaceControl for a java SurfaceControl. */
+extern SurfaceComposerClient::Transaction*
+android_view_SurfaceTransaction_getNativeSurfaceTransaction(JNIEnv* env,
+                                                            jobject surfaceTransactionObj);
+
+} // namespace android
+
+#endif // _ANDROID_VIEW_SURFACECONTROL_H
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1d2ce7e..060f440 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1662,13 +1662,18 @@
          darkening hysteresis constraint value is the n-th element of
          config_screenDarkeningThresholds.
 
+         Historically, it has been assumed that this will be an integer array with values in the
+         range of [0, 255]. However, it is now assumed to be a float array with values in the
+         range of [0, 1]. To accommodate both the possibilities, we internally check the scale on
+         which the thresholds are defined, and calibrate it accordingly.
+
          The (zero-based) index is calculated as follows: (MAX is the largest index of the array)
          condition                       calculated index
          value < level[0]                0
          level[n] <= value < level[n+1]  n+1
          level[MAX] <= value             MAX+1 -->
-    <integer-array name="config_screenThresholdLevels">
-    </integer-array>
+    <array name="config_screenThresholdLevels">
+    </array>
 
     <!-- Array of hysteresis constraint values for brightening, represented as tenths of a
          percent. The length of this array is assumed to be one greater than
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 4c247427..9e39e13 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -16,11 +16,12 @@
 
 package android.hardware.devicestate;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
@@ -36,7 +37,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -59,6 +59,7 @@
     public void setUp() {
         mService = new TestDeviceStateManagerService();
         mDeviceStateManagerGlobal = new DeviceStateManagerGlobal(mService);
+        assertFalse(mService.mCallbacks.isEmpty());
     }
 
     @Test
@@ -79,8 +80,8 @@
         verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback2).onStateChanged(eq(mService.getMergedState()));
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the supported states and verify callback
         mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE });
@@ -88,8 +89,8 @@
         verify(callback2).onSupportedStatesChanged(eq(mService.getSupportedStates()));
         mService.setSupportedStates(new int[]{ DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE });
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the base state and verify callback
         mService.setBaseState(OTHER_DEVICE_STATE);
@@ -98,8 +99,8 @@
         verify(callback2).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback2).onStateChanged(eq(mService.getMergedState()));
 
-        Mockito.reset(callback1);
-        Mockito.reset(callback2);
+        reset(callback1);
+        reset(callback2);
 
         // Change the requested state and verify callback
         DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
@@ -120,7 +121,7 @@
         verify(callback).onSupportedStatesChanged(eq(mService.getSupportedStates()));
         verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
         verify(callback).onStateChanged(eq(mService.getMergedState()));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.unregisterDeviceStateCallback(callback);
 
@@ -130,29 +131,19 @@
     }
 
     @Test
-    public void submittingRequestRegistersCallback() {
-        assertTrue(mService.mCallbacks.isEmpty());
-
-        DeviceStateRequest request = DeviceStateRequest.newBuilder(DEFAULT_DEVICE_STATE).build();
-        mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
-
-        assertFalse(mService.mCallbacks.isEmpty());
-    }
-
-    @Test
     public void submitRequest() {
         DeviceStateCallback callback = mock(DeviceStateCallback.class);
         mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
                 ConcurrentUtils.DIRECT_EXECUTOR);
 
         verify(callback).onStateChanged(eq(mService.getBaseState()));
-        Mockito.reset(callback);
+        reset(callback);
 
         DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
         mDeviceStateManagerGlobal.requestState(request, null /* executor */, null /* callback */);
 
         verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
@@ -160,6 +151,69 @@
     }
 
     @Test
+    public void submitBaseStateOverrideRequest() {
+        DeviceStateCallback callback = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+                ConcurrentUtils.DIRECT_EXECUTOR);
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        reset(callback);
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+                null /* callback */);
+
+        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+    }
+
+    @Test
+    public void submitBaseAndEmulatedStateOverride() {
+        DeviceStateCallback callback = mock(DeviceStateCallback.class);
+        mDeviceStateManagerGlobal.registerDeviceStateCallback(callback,
+                ConcurrentUtils.DIRECT_EXECUTOR);
+
+        verify(callback).onBaseStateChanged(eq(mService.getBaseState()));
+        verify(callback).onStateChanged(eq(mService.getBaseState()));
+        reset(callback);
+
+        DeviceStateRequest request = DeviceStateRequest.newBuilder(OTHER_DEVICE_STATE).build();
+        mDeviceStateManagerGlobal.requestBaseStateOverride(request, null /* executor */,
+                null /* callback */);
+
+        verify(callback).onBaseStateChanged(eq(OTHER_DEVICE_STATE));
+        verify(callback).onStateChanged(eq(OTHER_DEVICE_STATE));
+        assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+        reset(callback);
+
+        DeviceStateRequest secondRequest = DeviceStateRequest.newBuilder(
+                DEFAULT_DEVICE_STATE).build();
+
+        mDeviceStateManagerGlobal.requestState(secondRequest, null, null);
+
+        assertEquals(OTHER_DEVICE_STATE, mService.getBaseState());
+        verify(callback).onStateChanged(eq(DEFAULT_DEVICE_STATE));
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelStateRequest();
+
+        verify(callback).onStateChanged(OTHER_DEVICE_STATE);
+        reset(callback);
+
+        mDeviceStateManagerGlobal.cancelBaseStateOverride();
+
+        verify(callback).onBaseStateChanged(DEFAULT_DEVICE_STATE);
+        verify(callback).onStateChanged(DEFAULT_DEVICE_STATE);
+    }
+
+    @Test
     public void verifyDeviceStateRequestCallbacksCalled() {
         DeviceStateRequest.Callback callback = mock(TestDeviceStateRequestCallback.class);
 
@@ -169,7 +223,7 @@
                 callback /* callback */);
 
         verify(callback).onRequestActivated(eq(request));
-        Mockito.reset(callback);
+        reset(callback);
 
         mDeviceStateManagerGlobal.cancelStateRequest();
 
@@ -203,13 +257,16 @@
         private int[] mSupportedStates = new int[] { DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE };
         private int mBaseState = DEFAULT_DEVICE_STATE;
         private Request mRequest;
+        private Request mBaseStateRequest;
 
         private Set<IDeviceStateManagerCallback> mCallbacks = new HashSet<>();
 
         private DeviceStateInfo getInfo() {
+            final int mergedBaseState = mBaseStateRequest == null
+                    ? mBaseState : mBaseStateRequest.state;
             final int mergedState = mRequest == null
-                    ? mBaseState : mRequest.state;
-            return new DeviceStateInfo(mSupportedStates, mBaseState, mergedState);
+                    ? mergedBaseState : mRequest.state;
+            return new DeviceStateInfo(mSupportedStates, mergedBaseState, mergedState);
         }
 
         private void notifyDeviceStateInfoChanged() {
@@ -238,7 +295,7 @@
             try {
                 callback.onDeviceStateInfoChanged(getInfo());
             } catch (RemoteException e) {
-                // Do nothing. Should never happen.
+                e.rethrowFromSystemServer();
             }
         }
 
@@ -249,7 +306,7 @@
                     try {
                         callback.onRequestCanceled(mRequest.token);
                     } catch (RemoteException e) {
-                        // Do nothing. Should never happen.
+                        e.rethrowFromSystemServer();
                     }
                 }
             }
@@ -262,7 +319,7 @@
                 try {
                     callback.onRequestActive(token);
                 } catch (RemoteException e) {
-                    // Do nothing. Should never happen.
+                    e.rethrowFromSystemServer();
                 }
             }
         }
@@ -275,7 +332,46 @@
                 try {
                     callback.onRequestCanceled(token);
                 } catch (RemoteException e) {
-                    // Do nothing. Should never happen.
+                    e.rethrowFromSystemServer();
+                }
+            }
+            notifyDeviceStateInfoChanged();
+        }
+
+        @Override
+        public void requestBaseStateOverride(IBinder token, int state, int flags) {
+            if (mBaseStateRequest != null) {
+                for (IDeviceStateManagerCallback callback : mCallbacks) {
+                    try {
+                        callback.onRequestCanceled(mBaseStateRequest.token);
+                    } catch (RemoteException e) {
+                        e.rethrowFromSystemServer();
+                    }
+                }
+            }
+
+            final Request request = new Request(token, state, flags);
+            mBaseStateRequest = request;
+            notifyDeviceStateInfoChanged();
+
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestActive(token);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+        }
+
+        @Override
+        public void cancelBaseStateOverride() throws RemoteException {
+            IBinder token = mBaseStateRequest.token;
+            mBaseStateRequest = null;
+            for (IDeviceStateManagerCallback callback : mCallbacks) {
+                try {
+                    callback.onRequestCanceled(token);
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
                 }
             }
             notifyDeviceStateInfoChanged();
@@ -296,7 +392,7 @@
         }
 
         public int getBaseState() {
-            return mBaseState;
+            return getInfo().baseState;
         }
 
         public int getMergedState() {
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 940ca96..4679a9e 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -19,14 +19,15 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -51,8 +52,11 @@
 import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 
+import com.google.android.collect.Lists;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
 import java.nio.charset.StandardCharsets;
@@ -172,13 +176,61 @@
     }
 
     @Test
-    public void testGetEnabledTrustAgentsNotNull() throws RemoteException {
+    public void testSetEnabledTrustAgents() throws RemoteException {
         int testUserId = 10;
         ILockSettings ils = createTestLockSettings();
-        when(ils.getString(anyString(), any(), anyInt())).thenReturn("");
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetEnabledTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
         List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-        assertNotNull(trustAgents);
-        assertEquals(0, trustAgents.size());
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+    }
+
+    @Test
+    public void testSetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+        List<ComponentName> knownTrustAgents = Lists.newArrayList(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+        mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+        assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+    }
+
+    @Test
+    public void testGetKnownTrustAgents() throws RemoteException {
+        int testUserId = 10;
+        ILockSettings ils = createTestLockSettings();
+        when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+                "com.android/.TrustAgent,com.test/.TestAgent");
+
+        List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+        assertThat(trustAgents).containsExactly(
+                ComponentName.unflattenFromString("com.android/.TrustAgent"),
+                ComponentName.unflattenFromString("com.test/.TestAgent"));
     }
 
     private ILockSettings createTestLockSettings() {
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 682483d9..4f9af12 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -373,12 +373,6 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/TransitionController.java"
     },
-    "-1750384749": {
-      "message": "Launch on display check: allow launch on public display",
-      "level": "DEBUG",
-      "group": "WM_DEBUG_TASKS",
-      "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
-    },
     "-1750206390": {
       "message": "Exception thrown when creating surface for client %s (%s). %s",
       "level": "WARN",
@@ -907,6 +901,12 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1253056469": {
+      "message": "Launch on display check: %s launch for userId=%d on displayId=%d",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_TASKS",
+      "at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
+    },
     "-1248645819": {
       "message": "\tAdd container=%s",
       "level": "DEBUG",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 7e9c418..fb0a9db 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -41,7 +41,7 @@
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 2;
+        return 1;
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index febd791..74303e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -31,6 +31,7 @@
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -93,6 +94,7 @@
     }
 
     /** No longer overrides the animation if the transition is on the given Task. */
+    @GuardedBy("mLock")
     void stopOverrideSplitAnimation(int taskId) {
         if (mAnimationController != null) {
             mAnimationController.unregisterRemoteAnimations(taskId);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index c8ac0fc..00be5a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,8 +17,10 @@
 package androidx.window.extensions.embedding;
 
 import android.app.Activity;
+import android.content.res.Configuration;
 import android.util.Pair;
 import android.util.Size;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 
@@ -32,14 +34,18 @@
     private final TaskFragmentContainer mSecondaryContainer;
     @NonNull
     private final SplitRule mSplitRule;
+    @NonNull
+    private SplitAttributes mSplitAttributes;
 
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
-            @NonNull SplitRule splitRule) {
+            @NonNull SplitRule splitRule,
+            @NonNull SplitAttributes splitAttributes) {
         mPrimaryContainer = primaryContainer;
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
+        mSplitAttributes = splitAttributes;
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -72,6 +78,26 @@
         return mSplitRule;
     }
 
+    @NonNull
+    SplitAttributes getSplitAttributes() {
+        return mSplitAttributes;
+    }
+
+    /**
+     * Updates the {@link SplitAttributes} to this container.
+     * It is usually used when there's a folding state change or
+     * {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
+     * Configuration)}.
+     */
+    void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
+        mSplitAttributes = splitAttributes;
+    }
+
+    @NonNull
+    TaskContainer getTaskContainer() {
+        return getPrimaryContainer().getTaskContainer();
+    }
+
     /** Returns the minimum dimension pair of primary container and secondary container. */
     @NonNull
     Pair<Size, Size> getMinDimensionsPair() {
@@ -141,6 +167,7 @@
                 + " primaryContainer=" + mPrimaryContainer
                 + " secondaryContainer=" + mSecondaryContainer
                 + " splitRule=" + mSplitRule
+                + " splitAttributes" + mSplitAttributes
                 + "}";
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 126f835..203ece0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
@@ -40,12 +41,13 @@
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
+import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
+import android.app.Application;
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -62,19 +64,25 @@
 import android.util.Pair;
 import android.util.Size;
 import android.util.SparseArray;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -96,6 +104,23 @@
     @GuardedBy("mLock")
     private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
     /**
+     * A developer-defined {@link SplitAttributes} calculator to compute the current
+     * {@link SplitAttributes} with the current device and window states.
+     * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
+     * and unregistered via {@link #clearSplitAttributesCalculator()}.
+     * This is called when:
+     * <ul>
+     *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
+     *     WindowContainerTransaction)}</li>
+     *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
+     *   <li>Checking whether the place holder should be launched if there's a Activity matches
+     *   {@link SplitPlaceholderRule} </li>
+     * </ul>
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private SplitAttributesCalculator mSplitAttributesCalculator;
+    /**
      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
      * below it.
      * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -105,26 +130,65 @@
     @GuardedBy("mLock")
     final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
 
-    // Callback to Jetpack to notify about changes to split states.
-    @NonNull
+    /** Callback to Jetpack to notify about changes to split states. */
+    @Nullable
     private Consumer<List<SplitInfo>> mEmbeddingCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
     private final Handler mHandler;
     final Object mLock = new Object();
     private final ActivityStartMonitor mActivityStartMonitor;
+    @NonNull
+    final WindowLayoutComponentImpl mWindowLayoutComponent;
 
     public SplitController() {
+        this((WindowLayoutComponentImpl) Objects.requireNonNull(WindowExtensionsProvider
+                .getWindowExtensions().getWindowLayoutComponent()));
+    }
+
+    @VisibleForTesting
+    SplitController(@NonNull WindowLayoutComponentImpl windowLayoutComponent) {
         final MainThreadExecutor executor = new MainThreadExecutor();
         mHandler = executor.mHandler;
         mPresenter = new SplitPresenter(executor, this);
-        ActivityThread activityThread = ActivityThread.currentActivityThread();
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        final Application application = activityThread.getApplication();
         // Register a callback to be notified about activities being created.
-        activityThread.getApplication().registerActivityLifecycleCallbacks(
-                new LifecycleCallbacks());
+        application.registerActivityLifecycleCallbacks(new LifecycleCallbacks());
         // Intercept activity starts to route activities to new containers if necessary.
         Instrumentation instrumentation = activityThread.getInstrumentation();
+
         mActivityStartMonitor = new ActivityStartMonitor();
         instrumentation.addMonitor(mActivityStartMonitor);
+        mWindowLayoutComponent = windowLayoutComponent;
+        mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
+    }
+
+    private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> {
+        @Override
+        public void accept(List<CommonFoldingFeature> foldingFeatures) {
+            synchronized (mLock) {
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                for (int i = 0; i < mTaskContainers.size(); i++) {
+                    final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+                    if (!taskContainer.isVisible()) {
+                        continue;
+                    }
+                    if (taskContainer.getDisplayId() != DEFAULT_DISPLAY) {
+                        continue;
+                    }
+                    // TODO(b/238948678): Support reporting display features in all windowing modes.
+                    if (taskContainer.isInMultiWindow()) {
+                        continue;
+                    }
+                    if (taskContainer.isEmpty()) {
+                        continue;
+                    }
+                    updateContainersInTask(wct, taskContainer);
+                    updateAnimationOverride(taskContainer);
+                }
+                mPresenter.applyTransaction(wct);
+            }
+        }
     }
 
     /** Updates the embedding rules applied to future activity launches. */
@@ -141,12 +205,22 @@
 
     @Override
     public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
-        // TODO: Implement this method
+        synchronized (mLock) {
+            mSplitAttributesCalculator = calculator;
+        }
     }
 
     @Override
     public void clearSplitAttributesCalculator() {
-        // TODO: Implement this method
+        synchronized (mLock) {
+            mSplitAttributesCalculator = null;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    SplitAttributesCalculator getSplitAttributesCalculator() {
+        return mSplitAttributesCalculator;
     }
 
     @NonNull
@@ -191,7 +265,8 @@
                         onTaskFragmentVanished(wct, info);
                         break;
                     case TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED:
-                        onTaskFragmentParentInfoChanged(wct, taskId, change.getTaskConfiguration());
+                        onTaskFragmentParentInfoChanged(wct, taskId,
+                                change.getTaskFragmentParentInfo());
                         break;
                     case TYPE_TASK_FRAGMENT_ERROR:
                         final Bundle errorBundle = change.getErrorBundle();
@@ -346,22 +421,33 @@
      *
      * @param wct   The {@link WindowContainerTransaction} to make any changes with if needed.
      * @param taskId    Id of the parent Task that is changed.
-     * @param parentConfig  Config of the parent Task.
+     * @param parentInfo  {@link TaskFragmentParentInfo} of the parent Task.
      */
     @VisibleForTesting
     @GuardedBy("mLock")
     void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
-            int taskId, @NonNull Configuration parentConfig) {
-        onTaskConfigurationChanged(taskId, parentConfig);
-        if (isInPictureInPicture(parentConfig)) {
-            // No need to update presentation in PIP until the Task exit PIP.
-            return;
-        }
+            int taskId, @NonNull TaskFragmentParentInfo parentInfo) {
         final TaskContainer taskContainer = getTaskContainer(taskId);
         if (taskContainer == null || taskContainer.isEmpty()) {
             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
             return;
         }
+        taskContainer.updateTaskFragmentParentInfo(parentInfo);
+        if (!taskContainer.isVisible()) {
+            // Don't update containers if the task is not visible. We only update containers when
+            // parentInfo#isVisibleRequested is true.
+            return;
+        }
+        onTaskContainerInfoChanged(taskContainer, parentInfo.getConfiguration());
+        if (isInPictureInPicture(parentInfo.getConfiguration())) {
+            // No need to update presentation in PIP until the Task exit PIP.
+            return;
+        }
+        updateContainersInTask(wct, taskContainer);
+    }
+
+    private void updateContainersInTask(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskContainer taskContainer) {
         // Update all TaskFragments in the Task. Make a copy of the list since some may be
         // removed on updating.
         final List<TaskFragmentContainer> containers =
@@ -486,6 +572,7 @@
     }
 
     /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
+    @GuardedBy("mLock")
     private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
             final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -501,14 +588,11 @@
         }
     }
 
-    private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) {
-        final TaskContainer taskContainer = mTaskContainers.get(taskId);
-        if (taskContainer == null) {
-            return;
-        }
+    @GuardedBy("mLock")
+    private void onTaskContainerInfoChanged(@NonNull TaskContainer taskContainer,
+            @NonNull Configuration config) {
         final boolean wasInPip = taskContainer.isInPictureInPicture();
         final boolean isInPIp = isInPictureInPicture(config);
-        taskContainer.setWindowingMode(config.windowConfiguration.getWindowingMode());
 
         // We need to check the animation override when enter/exit PIP or has bounds changed.
         boolean shouldUpdateAnimationOverride = wasInPip != isInPIp;
@@ -526,37 +610,49 @@
      * Updates if we should override transition animation. We only want to override if the Task
      * bounds is large enough for at least one split rule.
      */
+    @GuardedBy("mLock")
     private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
         if (ENABLE_SHELL_TRANSITIONS) {
             // TODO(b/207070762): cleanup with legacy app transition
             // Animation will be handled by WM Shell with Shell transition enabled.
             return;
         }
-        if (!taskContainer.isTaskBoundsInitialized()
-                || !taskContainer.isWindowingModeInitialized()) {
+        if (!taskContainer.isTaskBoundsInitialized()) {
             // We don't know about the Task bounds/windowingMode yet.
             return;
         }
 
-        // We only want to override if it supports split.
-        if (supportSplit(taskContainer)) {
+        // We only want to override if the TaskContainer may show split.
+        if (mayShowSplit(taskContainer)) {
             mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
         } else {
             mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
         }
     }
 
-    private boolean supportSplit(@NonNull TaskContainer taskContainer) {
+    /** Returns whether the given {@link TaskContainer} may show in split. */
+    // Suppress GuardedBy warning because lint asks to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
         // No split inside PIP.
         if (taskContainer.isInPictureInPicture()) {
             return false;
         }
+        // Always assume the TaskContainer if SplitAttributesCalculator is set
+        if (mSplitAttributesCalculator != null) {
+            return true;
+        }
         // Check if the parent container bounds can support any split rule.
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof SplitRule)) {
                 continue;
             }
-            if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
+            final SplitRule splitRule = (SplitRule) rule;
+            final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+                    taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
+            if (shouldShowSplit(splitAttributes)) {
                 return true;
             }
         }
@@ -700,14 +796,18 @@
     /**
      * Starts an activity to side of the launchingActivity with the provided split config.
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @GuardedBy("mLock")
     private void startActivityToSide(@NonNull WindowContainerTransaction wct,
             @NonNull Activity launchingActivity, @NonNull Intent intent,
             @Nullable Bundle options, @NonNull SplitRule sideRule,
-            @Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
+            @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
+            boolean isPlaceholder) {
         try {
             mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
-                    isPlaceholder);
+                    splitAttributes, isPlaceholder);
         } catch (Exception e) {
             if (failureCallback != null) {
                 failureCallback.accept(e);
@@ -734,6 +834,10 @@
     }
 
     /** Whether the given new launched activity is in a split with a rule matched. */
+    // Suppress GuardedBy warning because lint asks to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
         final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(container);
@@ -827,8 +931,9 @@
         final TaskFragmentContainer primaryContainer = getContainerWithActivity(
                 primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
+        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
-                && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
+                && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
             final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -958,6 +1063,7 @@
      */
     @VisibleForTesting
     @Nullable
+    @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
         /*
@@ -1020,6 +1126,7 @@
     /**
      * Returns an empty expanded {@link TaskFragmentContainer} that we can launch an activity into.
      */
+    @GuardedBy("mLock")
     @Nullable
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@@ -1061,8 +1168,9 @@
         }
         final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
+        final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
         if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
-                && (canReuseContainer(splitRule, splitContainer.getSplitRule())
+                && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
                 // TODO(b/231845476) we should always respect clearTop.
                 || !respectClearTop)
                 && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1101,12 +1209,14 @@
         return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId);
     }
 
+    @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
                 activityInTask, taskId);
     }
 
+    @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
@@ -1130,7 +1240,7 @@
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
         if (!mTaskContainers.contains(taskId)) {
-            mTaskContainers.put(taskId, new TaskContainer(taskId));
+            mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask));
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
@@ -1142,10 +1252,6 @@
                 Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
             }
         }
-        if (!taskContainer.isWindowingModeInitialized()) {
-            taskContainer.setWindowingMode(activityInTask.getResources().getConfiguration()
-                    .windowConfiguration.getWindowingMode());
-        }
         updateAnimationOverride(taskContainer);
         return container;
     }
@@ -1154,12 +1260,16 @@
      * Creates and registers a new split with the provided containers and configuration. Finishes
      * existing secondary containers if found for the given primary container.
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     void registerSplit(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
             @NonNull TaskFragmentContainer secondaryContainer,
-            @NonNull SplitRule splitRule) {
+            @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
         final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
-                secondaryContainer, splitRule);
+                secondaryContainer, splitRule, splitAttributes);
         // Remove container later to prevent pinning escaping toast showing in lock task mode.
         if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
             removeExistingSecondaryContainers(wct, primaryContainer);
@@ -1310,6 +1420,12 @@
             // Skip position update - one or both containers are finished.
             return;
         }
+        final TaskContainer taskContainer = splitContainer.getTaskContainer();
+        final SplitRule splitRule = splitContainer.getSplitRule();
+        final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
+        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
+                taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
+        splitContainer.setSplitAttributes(splitAttributes);
         if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
             // Placeholder was finished, the positions will be updated when its container is emptied
             return;
@@ -1383,6 +1499,9 @@
         return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
     }
 
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @GuardedBy("mLock")
     boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
             @NonNull Activity activity, boolean isOnCreated) {
@@ -1409,18 +1528,20 @@
             return false;
         }
 
+        final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
                 placeholderRule.getPlaceholderIntent());
-        if (!shouldShowSideBySide(
-                mPresenter.getParentContainerBounds(activity), placeholderRule,
-                minDimensionsPair)) {
+        final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
+                placeholderRule, minDimensionsPair);
+        if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
 
         // TODO(b/190433398): Handle failed request
         final Bundle options = getPlaceholderOptions(activity, isOnCreated);
         startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
-                placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
+                placeholderRule, splitAttributes, null /* failureCallback */,
+                true /* isPlaceholder */);
         return true;
     }
 
@@ -1445,6 +1566,9 @@
         return options.toBundle();
     }
 
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @VisibleForTesting
     @GuardedBy("mLock")
     boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@@ -1457,11 +1581,10 @@
             // The placeholder should remain after it was first shown.
             return false;
         }
-
-        if (shouldShowSideBySide(splitContainer)) {
+        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+        if (SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
-
         mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
                 false /* shouldFinishDependent */);
         return true;
@@ -1471,6 +1594,7 @@
      * Returns the rule to launch a placeholder for the activity with the provided component name
      * if it is configured in the split config.
      */
+    @GuardedBy("mLock")
     private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof SplitPlaceholderRule)) {
@@ -1487,6 +1611,7 @@
     /**
      * Notifies listeners about changes to split states if necessary.
      */
+    @GuardedBy("mLock")
     private void updateCallbackIfNecessary() {
         if (mEmbeddingCallback == null) {
             return;
@@ -1508,6 +1633,7 @@
      * null, that indicates that the active split states are in an intermediate state and should
      * not be reported.
      */
+    @GuardedBy("mLock")
     @Nullable
     private List<SplitInfo> getActiveSplitStates() {
         List<SplitInfo> splitStates = new ArrayList<>();
@@ -1526,20 +1652,8 @@
                         .toActivityStack();
                 final ActivityStack secondaryContainer = container.getSecondaryContainer()
                         .toActivityStack();
-                final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
-                        ? new SplitAttributes.SplitType.RatioSplitType(
-                                container.getSplitRule().getSplitRatio())
-                        : new SplitAttributes.SplitType.ExpandContainersSplitType();
                 final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
-                        // Splits that are not showing side-by-side are reported as having 0 split
-                        // ratio, since by definition in the API the primary container occupies no
-                        // width of the split when covered by the secondary.
-                        // TODO(b/241042437): use v2 APIs for splitAttributes
-                        new SplitAttributes.Builder()
-                                .setSplitType(splitType)
-                                .setLayoutDirection(container.getSplitRule().getLayoutDirection())
-                                .build()
-                        );
+                        container.getSplitAttributes());
                 splitStates.add(splitState);
             }
         }
@@ -1577,6 +1691,7 @@
      * Returns a split rule for the provided pair of primary activity and secondary activity intent
      * if available.
      */
+    @GuardedBy("mLock")
     @Nullable
     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
             @NonNull Intent secondaryActivityIntent) {
@@ -1595,6 +1710,7 @@
     /**
      * Returns a split rule for the provided pair of primary and secondary activities if available.
      */
+    @GuardedBy("mLock")
     @Nullable
     private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
             @NonNull Activity secondaryActivity) {
@@ -1669,6 +1785,7 @@
      * Returns {@code true} if an Activity with the provided component name should always be
      * expanded to occupy full task bounds. Such activity must not be put in a split.
      */
+    @GuardedBy("mLock")
     private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof ActivityRule)) {
@@ -1694,6 +1811,10 @@
      * 'sticky' and the placeholder was finished when fully overlapping the primary container.
      * @return {@code true} if the associated container should be retained (and not be finished).
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(mPresenter.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
     boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
             @NonNull TaskFragmentContainer associatedContainer) {
         SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
@@ -1712,7 +1833,7 @@
         }
         // Decide whether the associated container should be retained based on the current
         // presentation mode.
-        if (shouldShowSideBySide(splitContainer)) {
+        if (shouldShowSplit(splitContainer)) {
             return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
         } else {
             return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
@@ -1905,23 +2026,33 @@
      * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
      * there is any.
      */
-    private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
+    private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
+            @NonNull WindowMetrics parentWindowMetrics) {
         if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
             return false;
         }
-        return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2);
+        return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+                parentWindowMetrics);
     }
 
     /** Whether the two rules have the same presentation. */
-    private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
-            @NonNull SplitPairRule rule2) {
+    @VisibleForTesting
+    static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+            @NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
+        if (rule1.getTag() != null || rule2.getTag() != null) {
+            // Tag must be unique if it is set. We don't want to reuse the container if the rules
+            // have different tags because they can have different SplitAttributes later through
+            // SplitAttributesCalculator.
+            return Objects.equals(rule1.getTag(), rule2.getTag());
+        }
+        // If both rules don't have tag, compare all SplitRules' properties that may affect their
+        // SplitAttributes.
         // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
-        return rule1.getSplitRatio() == rule2.getSplitRatio()
-                && rule1.getLayoutDirection() == rule2.getLayoutDirection()
-                && rule1.getFinishPrimaryWithSecondary()
-                == rule2.getFinishPrimaryWithSecondary()
-                && rule1.getFinishSecondaryWithPrimary()
-                == rule2.getFinishSecondaryWithPrimary();
+        return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
+                && rule1.checkParentMetrics(parentWindowMetrics)
+                == rule2.checkParentMetrics(parentWindowMetrics)
+                && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
+                && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 2ef8e4c..7960323 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -22,11 +22,11 @@
 import android.app.ActivityThread;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -42,9 +42,21 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
+import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams;
+import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -66,11 +78,25 @@
     })
     private @interface Position {}
 
+    private static final int CONTAINER_POSITION_LEFT = 0;
+    private static final int CONTAINER_POSITION_TOP = 1;
+    private static final int CONTAINER_POSITION_RIGHT = 2;
+    private static final int CONTAINER_POSITION_BOTTOM = 3;
+
+    @IntDef(value = {
+            CONTAINER_POSITION_LEFT,
+            CONTAINER_POSITION_TOP,
+            CONTAINER_POSITION_RIGHT,
+            CONTAINER_POSITION_BOTTOM,
+    })
+    private @interface ContainerPosition {}
+
     /**
      * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
      * Activity, Activity, Intent)}.
      * No need to expand the splitContainer because screen is big enough to
-     * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied.
+     * {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is
+     * satisfied.
      */
     static final int RESULT_NOT_EXPANDED = 0;
     /**
@@ -78,7 +104,7 @@
      * Activity, Activity, Intent)}.
      * The splitContainer should be expanded. It is usually because minimum dimensions is not
      * satisfied.
-     * @see #shouldShowSideBySide(Rect, SplitRule, Pair)
+     * @see #shouldShowSplit(SplitAttributes)
      */
     static final int RESULT_EXPANDED = 1;
     /**
@@ -101,6 +127,12 @@
     })
     private @interface ResultCode {}
 
+    @VisibleForTesting
+    static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES =
+            new SplitAttributes.Builder()
+            .setSplitType(new ExpandContainersSplitType())
+            .build();
+
     private final SplitController mController;
 
     SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
@@ -129,14 +161,17 @@
      * @return The newly created secondary container.
      */
     @NonNull
+    @GuardedBy("mController.mLock")
     TaskFragmentContainer createNewSplitWithEmptySideContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
             @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
-        final Rect parentBounds = getParentContainerBounds(primaryActivity);
+        final TaskProperties taskProperties = getTaskProperties(primaryActivity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
                 primaryActivity, secondaryIntent);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+                minDimensionsPair);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
                 primaryActivity, primaryRectBounds, null);
 
@@ -144,8 +179,8 @@
         final int taskId = primaryContainer.getTaskId();
         final TaskFragmentContainer secondaryContainer = mController.newContainer(
                 secondaryIntent, primaryActivity, taskId);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
-                rule, primaryActivity, minDimensionsPair);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final int windowingMode = mController.getTaskContainer(taskId)
                 .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
         createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -154,9 +189,10 @@
 
         // Set adjacent to each other so that the containers below will be invisible.
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
 
-        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+                splitAttributes);
 
         return secondaryContainer;
     }
@@ -176,16 +212,18 @@
     void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
             @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
             @NonNull SplitPairRule rule) {
-        final Rect parentBounds = getParentContainerBounds(primaryActivity);
+        final TaskProperties taskProperties = getTaskProperties(primaryActivity);
         final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
                 secondaryActivity);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
+                minDimensionsPair);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
                 primaryActivity, primaryRectBounds, null);
 
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                primaryActivity, minDimensionsPair);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity);
         TaskFragmentContainer containerToAvoid = primaryContainer;
@@ -200,9 +238,10 @@
 
         // Set adjacent to each other so that the containers below will be invisible.
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
 
-        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
+        mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
+                splitAttributes);
     }
 
     /**
@@ -244,16 +283,16 @@
      * @param rule              The split rule to be applied to the container.
      * @param isPlaceholder     Whether the launch is a placeholder.
      */
+    @GuardedBy("mController.mLock")
     void startActivityToSide(@NonNull WindowContainerTransaction wct,
             @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
-            @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
-        final Rect parentBounds = getParentContainerBounds(launchingActivity);
-        final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
-                launchingActivity, activityIntent);
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                launchingActivity, minDimensionsPair);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                launchingActivity, minDimensionsPair);
+            @Nullable Bundle activityOptions, @NonNull SplitRule rule,
+            @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
+        final TaskProperties taskProperties = getTaskProperties(launchingActivity);
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
 
         TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
                 launchingActivity);
@@ -268,7 +307,7 @@
         final int windowingMode = mController.getTaskContainer(taskId)
                 .getWindowingModeForSplitTaskFragment(primaryRectBounds);
         mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
-                rule);
+                rule, splitAttributes);
         startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
                 launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
                 activityIntent, activityOptions, rule, windowingMode);
@@ -284,22 +323,24 @@
      * @param updatedContainer The task fragment that was updated and caused this split update.
      * @param wct WindowContainerTransaction that this update should be performed with.
      */
+    @GuardedBy("mController.mLock")
     void updateSplitContainer(@NonNull SplitContainer splitContainer,
             @NonNull TaskFragmentContainer updatedContainer,
             @NonNull WindowContainerTransaction wct) {
-        // Getting the parent bounds using the updated container - it will have the recent value.
-        final Rect parentBounds = getParentContainerBounds(updatedContainer);
+        // Getting the parent configuration using the updated container - it will have the recent
+        // value.
         final SplitRule rule = splitContainer.getSplitRule();
         final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
         final Activity activity = primaryContainer.getTopNonFinishingActivity();
         if (activity == null) {
             return;
         }
-        final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
-        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
-                activity, minDimensionsPair);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
-                activity, minDimensionsPair);
+        final TaskProperties taskProperties = getTaskProperties(updatedContainer);
+        final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
+        final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
+                splitAttributes);
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
+                splitAttributes);
         final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
         // Whether the placeholder is becoming side-by-side with the primary from fullscreen.
         final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
@@ -311,7 +352,7 @@
         resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
         resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
         setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
-                minDimensionsPair);
+                splitAttributes);
         if (isPlaceholderBecomingSplit) {
             // When placeholder is shown in split, we should keep the focus on the primary.
             wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -323,14 +364,14 @@
         updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
     }
 
+    @GuardedBy("mController.mLock")
     private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer primaryContainer,
             @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
-            @NonNull Pair<Size, Size> minDimensionsPair) {
-        final Rect parentBounds = getParentContainerBounds(primaryContainer);
+            @NonNull SplitAttributes splitAttributes) {
         // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
         // secondaryContainer could not be finished.
-        if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) {
+        if (!shouldShowSplit(splitAttributes)) {
             setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
                     null /* secondary */, null /* splitRule */);
         } else {
@@ -416,8 +457,9 @@
      * Expands the split container if the current split bounds are smaller than the Activity or
      * Intent that is added to the container.
      *
-     * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)}
-     * and if {@link android.window.TaskFragmentInfo} has reported to the client side.
+     * @return the {@link ResultCode} based on
+     * {@link #shouldShowSplit(SplitAttributes)} and if
+     * {@link android.window.TaskFragmentInfo} has reported to the client side.
      */
     @ResultCode
     int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
@@ -427,7 +469,6 @@
             throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
                     + " non-null.");
         }
-        final Rect taskBounds = getParentContainerBounds(primaryActivity);
         final Pair<Size, Size> minDimensionsPair;
         if (secondaryActivity != null) {
             minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
@@ -436,7 +477,12 @@
                     secondaryIntent);
         }
         // Expand the splitContainer if minimum dimensions are not satisfied.
-        if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
+        final TaskContainer taskContainer = splitContainer.getTaskContainer();
+        final SplitAttributes splitAttributes = sanitizeSplitAttributes(
+                taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
+                minDimensionsPair);
+        splitContainer.setSplitAttributes(splitAttributes);
+        if (!shouldShowSplit(splitAttributes)) {
             // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
             // bounds. Return failure to create a new SplitContainer which fills task bounds.
             if (splitContainer.getPrimaryContainer().getInfo() == null
@@ -450,36 +496,63 @@
         return RESULT_NOT_EXPANDED;
     }
 
-    static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
-        return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
+    static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
+        return shouldShowSplit(splitContainer.getSplitAttributes());
     }
 
-    static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
-        final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
-
-        return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
-                splitContainer.getMinDimensionsPair());
+    static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
+        return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
     }
 
-    static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule,
+    @GuardedBy("mController.mLock")
+    @NonNull
+    SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
+            @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+        final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
+        final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
+        final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
+        if (calculator == null) {
+            if (!isDefaultMinSizeSatisfied) {
+                return EXPAND_CONTAINERS_ATTRIBUTES;
+            }
+            return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
+                    minDimensionsPair);
+        }
+        final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent
+                .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+                        taskConfiguration.windowConfiguration);
+        final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
+                taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
+                isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
+        final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
+        return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
+    }
+
+    /**
+     * Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't
+     * meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns
+     * the passed {@link SplitAttributes}.
+     */
+    @NonNull
+    private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
-        // TODO(b/190433398): Supply correct insets.
-        final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
-                new WindowInsets(new Rect()));
-        // Don't show side by side if bounds is not qualified.
-        if (!rule.checkParentMetrics(parentMetrics)) {
-            return false;
-        }
-        final float splitRatio = rule.getSplitRatio();
-        // We only care the size of the bounds regardless of its position.
-        final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */);
-        final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */);
-
         if (minDimensionsPair == null) {
-            return true;
+            return splitAttributes;
         }
-        return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
-                && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second);
+        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
+                foldingFeature);
+        final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes,
+                foldingFeature);
+        if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
+                || boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) {
+            return EXPAND_CONTAINERS_ATTRIBUTES;
+        }
+        return splitAttributes;
     }
 
     @NonNull
@@ -541,20 +614,25 @@
 
     @VisibleForTesting
     @NonNull
-    static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds,
-            @NonNull SplitRule rule, @NonNull Activity primaryActivity,
-            @Nullable Pair<Size, Size> minDimensionsPair) {
-        if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) {
+    Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
+            @NonNull SplitAttributes splitAttributes) {
+        final Configuration taskConfiguration = taskProperties.getConfiguration();
+        final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
+        final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
+                foldingFeature);
+        final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
+                .setSplitType(splitType)
+                .setLayoutDirection(splitAttributes.getLayoutDirection())
+                .build();
+        if (!shouldShowSplit(computedSplitAttributes)) {
             return new Rect();
         }
-        final boolean isLtr = isLtr(primaryActivity, rule);
-        final float splitRatio = rule.getSplitRatio();
-
         switch (position) {
             case POSITION_START:
-                return getPrimaryBounds(parentBounds, splitRatio, isLtr);
+                return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
             case POSITION_END:
-                return getSecondaryBounds(parentBounds, splitRatio, isLtr);
+                return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
+                        foldingFeature);
             case POSITION_FILL:
             default:
                 return new Rect();
@@ -562,74 +640,303 @@
     }
 
     @NonNull
-    private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio,
-            boolean isLtr) {
-        return isLtr ? getLeftContainerBounds(parentBounds, splitRatio)
-                : getRightContainerBounds(parentBounds, 1 - splitRatio);
-    }
-
-    @NonNull
-    private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio,
-            boolean isLtr) {
-        return isLtr ? getRightContainerBounds(parentBounds, splitRatio)
-                : getLeftContainerBounds(parentBounds, 1 - splitRatio);
-    }
-
-    private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
-        return new Rect(
-                parentBounds.left,
-                parentBounds.top,
-                (int) (parentBounds.left + parentBounds.width() * splitRatio),
-                parentBounds.bottom);
-    }
-
-    private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
-        return new Rect(
-                (int) (parentBounds.left + parentBounds.width() * splitRatio),
-                parentBounds.top,
-                parentBounds.right,
-                parentBounds.bottom);
-    }
-
-    /**
-     * Checks if a split with the provided rule should be displays in left-to-right layout
-     * direction, either always or with the current configuration.
-     */
-    private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
-        switch (rule.getLayoutDirection()) {
-            case LayoutDirection.LOCALE:
-                return context.getResources().getConfiguration().getLayoutDirection()
+    private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        if (!shouldShowSplit(splitAttributes)) {
+            return new Rect();
+        }
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+                return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+                return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.LOCALE: {
+                final boolean isLtr = taskConfiguration.getLayoutDirection()
                         == View.LAYOUT_DIRECTION_LTR;
-            case LayoutDirection.RTL:
-                return false;
-            case LayoutDirection.LTR:
+                return isLtr
+                        ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
+                        : getRightContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+                return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+                return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
             default:
-                return true;
+                throw new IllegalArgumentException("Unknown layout direction:"
+                        + splitAttributes.getLayoutDirection());
         }
     }
 
     @NonNull
-    static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
-        return container.getTaskContainer().getTaskBounds();
+    private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        if (!shouldShowSplit(splitAttributes)) {
+            return new Rect();
+        }
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
+                return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
+                return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.LOCALE: {
+                final boolean isLtr = taskConfiguration.getLayoutDirection()
+                        == View.LAYOUT_DIRECTION_LTR;
+                return isLtr
+                        ? getRightContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature)
+                        : getLeftContainerBounds(taskConfiguration, splitAttributes,
+                                foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
+                return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
+                return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
+            }
+            default:
+                throw new IllegalArgumentException("Unknown layout direction:"
+                        + splitAttributes.getLayoutDirection());
+        }
     }
 
     @NonNull
-    Rect getParentContainerBounds(@NonNull Activity activity) {
-        final TaskFragmentContainer container = mController.getContainerWithActivity(activity);
-        if (container != null) {
-            return getParentContainerBounds(container);
-        }
-        // Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
-        return getNonEmbeddedActivityBounds(activity);
+    private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_LEFT, foldingFeature);
+        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
+    }
+
+    @NonNull
+    private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_RIGHT, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom);
+    }
+
+    @NonNull
+    private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_TOP, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom);
+    }
+
+    @NonNull
+    private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
+        final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
+                CONTAINER_POSITION_BOTTOM, foldingFeature);
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom);
     }
 
     /**
-     * Obtains the bounds from a non-embedded Activity.
-     * <p>
-     * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most
-     * cases unless we want to obtain task bounds before
-     * {@link TaskContainer#isTaskBoundsInitialized()}.
+     * Computes the boundary position between the primary and the secondary containers for the given
+     * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states.
+     * <ol>
+     *     <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom
+     *       container, which is {@link Rect#bottom} of the top container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top
+     *       container, which is {@link Rect#top} of the bottom container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right
+     *       container, which is {@link Rect#right} of the left container bounds.</li>
+     *     <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom
+     *       container, which is {@link Rect#left} of the right container bounds.</li>
+     * </ol>
+     *
+     * @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature)
+     * @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature)
      */
+    private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration,
+            @NonNull SplitAttributes splitAttributes, @ContainerPosition int position,
+            @Nullable FoldingFeature foldingFeature) {
+        final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
+        final int startPoint = shouldSplitHorizontally(splitAttributes)
+                ? parentBounds.top
+                : parentBounds.left;
+        final int dimen = shouldSplitHorizontally(splitAttributes)
+                ? parentBounds.height()
+                : parentBounds.width();
+        final SplitType splitType = splitAttributes.getSplitType();
+        if (splitType instanceof RatioSplitType) {
+            final RatioSplitType splitRatio = (RatioSplitType) splitType;
+            return (int) (startPoint + dimen * splitRatio.getRatio());
+        }
+        // At this point, SplitType must be a HingeSplitType and foldingFeature must be
+        // non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier.
+        Objects.requireNonNull(foldingFeature);
+        if (!(splitType instanceof HingeSplitType)) {
+            throw new IllegalArgumentException("Unknown splitType:" + splitType);
+        }
+        final Rect hingeArea = foldingFeature.getBounds();
+        switch (position) {
+            case CONTAINER_POSITION_LEFT:
+                return hingeArea.left;
+            case CONTAINER_POSITION_TOP:
+                return hingeArea.top;
+            case CONTAINER_POSITION_RIGHT:
+                return hingeArea.right;
+            case CONTAINER_POSITION_BOTTOM:
+                return hingeArea.bottom;
+            default:
+                throw new IllegalArgumentException("Unknown position:" + position);
+        }
+    }
+
+    @Nullable
+    private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
+        final int displayId = taskProperties.getDisplayId();
+        final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
+                .windowConfiguration;
+        final WindowLayoutInfo info = mController.mWindowLayoutComponent
+                .getCurrentWindowLayoutInfo(displayId, windowConfiguration);
+        final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
+        if (displayFeatures.isEmpty()) {
+            return null;
+        }
+        final List<FoldingFeature> foldingFeatures = new ArrayList<>();
+        for (DisplayFeature displayFeature : displayFeatures) {
+            if (displayFeature instanceof FoldingFeature) {
+                foldingFeatures.add((FoldingFeature) displayFeature);
+            }
+        }
+        // TODO(b/240219484): Support device with multiple hinges.
+        if (foldingFeatures.size() != 1) {
+            return null;
+        }
+        return foldingFeatures.get(0);
+    }
+
+    /**
+     * Indicates that this {@link SplitAttributes} splits the task horizontally. Returns
+     * {@code false} if this {@link SplitAttributes} splits the task vertically.
+     */
+    private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) {
+        switch (splitAttributes.getLayoutDirection()) {
+            case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+            case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and
+     * window state.
+     * If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed
+     * the ratio if the computed {@link SplitAttributes#getLayoutDirection} is
+     * {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or
+     * {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier.
+     * If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks
+     * the current device and window states to determine whether the split container should split
+     * by hinge or use {@link HingeSplitType#getFallbackSplitType}.
+     */
+    private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
+            @NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
+        final int layoutDirection = splitAttributes.getLayoutDirection();
+        final SplitType splitType = splitAttributes.getSplitType();
+        if (splitType instanceof ExpandContainersSplitType) {
+            return splitType;
+        } else if (splitType instanceof RatioSplitType) {
+            final RatioSplitType splitRatio = (RatioSplitType) splitType;
+            // Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
+            // computation have the same direction, which is from (top, left) to (bottom, right).
+            final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
+            switch (layoutDirection) {
+                case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
+                case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
+                    return splitType;
+                case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
+                case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
+                    return reversedSplitType;
+                case LayoutDirection.LOCALE: {
+                    boolean isLtr = taskConfiguration.getLayoutDirection()
+                            == View.LAYOUT_DIRECTION_LTR;
+                    return isLtr ? splitType : reversedSplitType;
+                }
+            }
+        } else if (splitType instanceof HingeSplitType) {
+            final HingeSplitType hinge = (HingeSplitType) splitType;
+            @WindowingMode
+            final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode();
+            return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode)
+                    ? hinge : hinge.getFallbackSplitType();
+        }
+        throw new IllegalArgumentException("Unknown SplitType:" + splitType);
+    }
+
+    private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes,
+            @Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) {
+        // Only HingeSplitType may split the task bounds by hinge.
+        if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) {
+            return false;
+        }
+        // Device is not foldable, so there's no hinge to match.
+        if (foldingFeature == null) {
+            return false;
+        }
+        // The task is in multi-window mode. Match hinge doesn't make sense because current task
+        // bounds may not fit display bounds.
+        if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) {
+            return false;
+        }
+        // Return true if how the split attributes split the task bounds matches the orientation of
+        // folding area orientation.
+        return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature);
+    }
+
+    private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) {
+        final Rect bounds = foldingFeature.getBounds();
+        return bounds.width() > bounds.height();
+    }
+
+    @NonNull
+    static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
+        return container.getTaskContainer().getTaskProperties();
+    }
+
+    @NonNull
+    TaskProperties getTaskProperties(@NonNull Activity activity) {
+        final TaskContainer taskContainer = mController.getTaskContainer(
+                mController.getTaskId(activity));
+        if (taskContainer != null) {
+            return taskContainer.getTaskProperties();
+        }
+        // Use a copy of configuration because activity's configuration may be updated later,
+        // or we may get unexpected TaskContainer's configuration if Activity's configuration is
+        // updated. An example is Activity is going to be in split.
+        return new TaskProperties(activity.getDisplayId(),
+                new Configuration(activity.getResources().getConfiguration()));
+    }
+
+    @NonNull
+    WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
+        return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
+    }
+
+    @NonNull
+    private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
+        // TODO(b/190433398): Supply correct insets.
+        return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
+    }
+
+    /** Obtains the bounds from a non-embedded Activity. */
     @NonNull
     static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
         final WindowConfiguration windowConfiguration =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index b563677..91573ff 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -24,10 +24,13 @@
 import android.app.Activity;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
+import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -42,13 +45,10 @@
     /** The unique task id. */
     private final int mTaskId;
 
+    // TODO(b/240219484): consolidate to mConfiguration
     /** Available window bounds of this Task. */
     private final Rect mTaskBounds = new Rect();
 
-    /** Windowing mode of this Task. */
-    @WindowingMode
-    private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
-
     /** Active TaskFragments in this Task. */
     @NonNull
     final List<TaskFragmentContainer> mContainers = new ArrayList<>();
@@ -57,24 +57,56 @@
     @NonNull
     final List<SplitContainer> mSplitContainers = new ArrayList<>();
 
+    @NonNull
+    private final Configuration mConfiguration;
+
+    private int mDisplayId;
+
+    private boolean mIsVisible;
+
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
-     * the organizer receives {@link SplitController#onTaskFragmentVanished(TaskFragmentInfo)} event
-     * for them.
+     * the organizer receives
+     * {@link SplitController#onTaskFragmentVanished(WindowContainerTransaction, TaskFragmentInfo)}
+     * event for them.
      */
     final Set<IBinder> mFinishedContainer = new ArraySet<>();
 
-    TaskContainer(int taskId) {
+    /**
+     * The {@link TaskContainer} constructor
+     *
+     * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with
+     *               {@code activityInTask}.
+     * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
+     *                       initialize the {@link TaskContainer} properties.
+     *
+     */
+    TaskContainer(int taskId, @NonNull Activity activityInTask) {
         if (taskId == INVALID_TASK_ID) {
             throw new IllegalArgumentException("Invalid Task id");
         }
         mTaskId = taskId;
+        // Make a copy in case the activity's config is updated, and updates the TaskContainer's
+        // config unexpectedly.
+        mConfiguration = new Configuration(activityInTask.getResources().getConfiguration());
+        mDisplayId = activityInTask.getDisplayId();
+        // Note that it is always called when there's a new Activity is started, which implies
+        // the host task is visible.
+        mIsVisible = true;
     }
 
     int getTaskId() {
         return mTaskId;
     }
 
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    boolean isVisible() {
+        return mIsVisible;
+    }
+
     @NonNull
     Rect getTaskBounds() {
         return mTaskBounds;
@@ -94,13 +126,21 @@
         return !mTaskBounds.isEmpty();
     }
 
-    void setWindowingMode(int windowingMode) {
-        mWindowingMode = windowingMode;
+    @NonNull
+    Configuration getConfiguration() {
+        // Make a copy in case the config is updated unexpectedly.
+        return new Configuration(mConfiguration);
     }
 
-    /** Whether the Task windowing mode has been initialized. */
-    boolean isWindowingModeInitialized() {
-        return mWindowingMode != WINDOWING_MODE_UNDEFINED;
+    @NonNull
+    TaskProperties getTaskProperties() {
+        return new TaskProperties(mDisplayId, mConfiguration);
+    }
+
+    void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
+        mConfiguration.setTo(info.getConfiguration());
+        mDisplayId = info.getDisplayId();
+        mIsVisible = info.isVisibleRequested();
     }
 
     /**
@@ -123,13 +163,20 @@
         // DecorCaptionView won't work correctly. As a result, have the TaskFragment to be in the
         // Task windowing mode if the Task is in multi window.
         // TODO we won't need this anymore after we migrate Freeform caption to WM Shell.
-        return WindowConfiguration.inMultiWindowMode(mWindowingMode)
-                ? mWindowingMode
-                : WINDOWING_MODE_MULTI_WINDOW;
+        return isInMultiWindow() ? getWindowingMode() : WINDOWING_MODE_MULTI_WINDOW;
     }
 
     boolean isInPictureInPicture() {
-        return mWindowingMode == WINDOWING_MODE_PINNED;
+        return getWindowingMode() == WINDOWING_MODE_PINNED;
+    }
+
+    boolean isInMultiWindow() {
+        return WindowConfiguration.inMultiWindowMode(getWindowingMode());
+    }
+
+    @WindowingMode
+    private int getWindowingMode() {
+        return getConfiguration().windowConfiguration.getWindowingMode();
     }
 
     /** Whether there is any {@link TaskFragmentContainer} below this Task. */
@@ -173,4 +220,28 @@
     int indexOf(@NonNull TaskFragmentContainer child) {
         return mContainers.indexOf(child);
     }
+
+    /**
+     * A wrapper class which contains the display ID and {@link Configuration} of a
+     * {@link TaskContainer}
+     */
+    static final class TaskProperties {
+        private final int mDisplayId;
+        @NonNull
+        private final Configuration mConfiguration;
+
+        TaskProperties(int displayId, @NonNull Configuration configuration) {
+            mDisplayId = displayId;
+            mConfiguration = configuration;
+        }
+
+        int getDisplayId() {
+            return mDisplayId;
+        }
+
+        @NonNull
+        Configuration getConfiguration() {
+            return mConfiguration;
+        }
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index f24401f..c76f568 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -47,6 +47,7 @@
 import androidx.window.util.DataProducer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -68,6 +69,8 @@
 
     private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
 
+    private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
+
     private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
             new ArrayMap<>();
 
@@ -80,6 +83,11 @@
         mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
+    /** Registers to listen to {@link CommonFoldingFeature} changes */
+    public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
+        mFoldingFeatureProducer.addDataChangedCallback(consumer);
+    }
+
     /**
      * Adds a listener interested in receiving updates to {@link WindowLayoutInfo}
      *
@@ -186,6 +194,8 @@
     }
 
     private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+        mLastReportedFoldingFeatures.clear();
+        mLastReportedFoldingFeatures.addAll(storedFeatures);
         for (Context context : getContextsListeningForLayoutChanges()) {
             // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
             Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
@@ -207,6 +217,27 @@
     }
 
     /**
+     * Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}.
+     *
+     * @return current {@link WindowLayoutInfo} on the default display. Returns
+     *   empty {@link WindowLayoutInfo} on secondary displays.
+     */
+    @NonNull
+    public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId,
+            @NonNull WindowConfiguration windowConfiguration) {
+        return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures);
+    }
+
+    /** @see #getWindowLayoutInfo(Context, List)  */
+    private WindowLayoutInfo getWindowLayoutInfo(int displayId,
+            @NonNull WindowConfiguration windowConfiguration,
+            List<CommonFoldingFeature> storedFeatures) {
+        List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration,
+                storedFeatures);
+        return new WindowLayoutInfo(displayFeatureList);
+    }
+
+    /**
      * Translate from the {@link CommonFoldingFeature} to
      * {@link DisplayFeature} for a given {@link Activity}. If a
      * {@link CommonFoldingFeature} is not valid then it will be omitted.
@@ -225,12 +256,23 @@
      */
     private List<DisplayFeature> getDisplayFeatures(
             @NonNull @UiContext Context context, List<CommonFoldingFeature> storedFeatures) {
-        List<DisplayFeature> features = new ArrayList<>();
         if (!shouldReportDisplayFeatures(context)) {
+            return Collections.emptyList();
+        }
+        return getDisplayFeatures(context.getDisplayId(),
+                context.getResources().getConfiguration().windowConfiguration,
+                storedFeatures);
+    }
+
+    /** @see #getDisplayFeatures(Context, List) */
+    private List<DisplayFeature> getDisplayFeatures(int displayId,
+            @NonNull WindowConfiguration windowConfiguration,
+            List<CommonFoldingFeature> storedFeatures) {
+        List<DisplayFeature> features = new ArrayList<>();
+        if (displayId != DEFAULT_DISPLAY) {
             return features;
         }
 
-        int displayId = context.getDisplay().getDisplayId();
         for (CommonFoldingFeature baseFeature : storedFeatures) {
             Integer state = convertToExtensionState(baseFeature.getState());
             if (state == null) {
@@ -238,7 +280,7 @@
             }
             Rect featureRect = baseFeature.getRect();
             rotateRectToDisplayRotation(displayId, featureRect);
-            transformToWindowSpaceRect(context, featureRect);
+            transformToWindowSpaceRect(windowConfiguration, featureRect);
 
             if (!isZero(featureRect)) {
                 // TODO(b/228641877): Remove guarding when fixed.
@@ -263,6 +305,8 @@
             windowingMode = ActivityClient.getInstance().getTaskWindowingMode(
                     context.getActivityToken());
         } else {
+            // TODO(b/242674941): use task windowing mode for window context that associates with
+            //  activity.
             windowingMode = context.getResources().getConfiguration().windowConfiguration
                     .getWindowingMode();
         }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 31bf963..9e2611f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -21,6 +21,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
@@ -89,13 +90,21 @@
     /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
     public static void transformToWindowSpaceRect(@NonNull @UiContext Context context,
             Rect inOutRect) {
-        Rect windowRect = getWindowBounds(context);
-        if (!Rect.intersects(inOutRect, windowRect)) {
+        transformToWindowSpaceRect(getWindowBounds(context), inOutRect);
+    }
+
+    /** @see ExtensionHelper#transformToWindowSpaceRect(Context, Rect) */
+    public static void transformToWindowSpaceRect(@NonNull WindowConfiguration windowConfiguration,
+            Rect inOutRect) {
+        transformToWindowSpaceRect(windowConfiguration.getBounds(), inOutRect);
+    }
+
+    private static void transformToWindowSpaceRect(@NonNull Rect bounds, @NonNull Rect inOutRect) {
+        if (!inOutRect.intersect(bounds)) {
             inOutRect.setEmpty();
             return;
         }
-        inOutRect.intersect(windowRect);
-        inOutRect.offset(-windowRect.left, -windowRect.top);
+        inOutRect.offset(-bounds.left, -bounds.top);
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index effc1a3..40f7a27 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -16,9 +16,12 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
 import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
 
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import android.annotation.NonNull;
@@ -26,32 +29,68 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Pair;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerToken;
 
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 public class EmbeddingTestUtils {
     static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
     static final int TASK_ID = 10;
-    static final float SPLIT_RATIO = 0.5f;
+    static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually();
+    static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build();
+    static final String TEST_TAG = "test";
     /** Default finish behavior in Jetpack. */
     static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
     static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
+    private static final float SPLIT_RATIO = 0.5f;
 
     private EmbeddingTestUtils() {}
 
     /** Gets the bounds of a TaskFragment that is in split. */
     static Rect getSplitBounds(boolean isPrimary) {
-        final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO);
+        return getSplitBounds(isPrimary, false /* shouldSplitHorizontally */);
+    }
+
+    /** Gets the bounds of a TaskFragment that is in split. */
+    static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) {
+        final int dimension = (int) (
+                (shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width())
+                        * SPLIT_RATIO);
+        if (shouldSplitHorizontally) {
+            return isPrimary
+                    ? new Rect(
+                            TASK_BOUNDS.left,
+                            TASK_BOUNDS.top,
+                            TASK_BOUNDS.right,
+                            TASK_BOUNDS.top + dimension)
+                    : new Rect(
+                            TASK_BOUNDS.left,
+                            TASK_BOUNDS.top + dimension,
+                            TASK_BOUNDS.right,
+                            TASK_BOUNDS.bottom);
+        }
         return isPrimary
-                ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
-                TASK_BOUNDS.bottom)
+                ? new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.left + dimension,
+                        TASK_BOUNDS.bottom)
                 : new Rect(
-                        TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
+                        TASK_BOUNDS.left + dimension,
+                        TASK_BOUNDS.top,
+                        TASK_BOUNDS.right,
                         TASK_BOUNDS.bottom);
     }
 
@@ -69,10 +108,15 @@
                 activityPair -> false,
                 targetPair::equals,
                 w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(
+                        new SplitAttributes.Builder()
+                                .setSplitType(SPLIT_TYPE)
+                                .build()
+                )
                 .setShouldClearTop(clearTop)
                 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
                 .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setTag(TEST_TAG)
                 .build();
     }
 
@@ -101,10 +145,15 @@
                 targetPair::equals,
                 activityIntentPair -> false,
                 w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(
+                        new SplitAttributes.Builder()
+                                .setSplitType(SPLIT_TYPE)
+                                .build()
+                )
                 .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
                 .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
                 .setShouldClearTop(clearTop)
+                .setTag(TEST_TAG)
                 .build();
     }
 
@@ -130,4 +179,29 @@
                 primaryBounds.width() + 1, primaryBounds.height() + 1);
         return aInfo;
     }
+
+    static TaskContainer createTestTaskContainer() {
+        Resources resources = mock(Resources.class);
+        doReturn(new Configuration()).when(resources).getConfiguration();
+        Activity activity = mock(Activity.class);
+        doReturn(resources).when(activity).getResources();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+
+        return new TaskContainer(TASK_ID, activity);
+    }
+
+    static WindowLayoutInfo createWindowLayoutInfo() {
+        final FoldingFeature foldingFeature = new FoldingFeature(
+                new Rect(
+                        TASK_BOUNDS.left,
+                        TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5,
+                        TASK_BOUNDS.right,
+                        TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5
+                        ),
+                FoldingFeature.TYPE_HINGE,
+                FoldingFeature.STATE_HALF_OPENED);
+        final List<DisplayFeature> displayFeatures = new ArrayList<>();
+        displayFeatures.add(foldingFeature);
+        return new WindowLayoutInfo(displayFeatures);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 58a627b..957a248 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -26,12 +27,14 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Point;
+import android.os.Handler;
 import android.platform.test.annotations.Presubmit;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentTransaction;
@@ -65,7 +68,10 @@
     private WindowContainerTransaction mTransaction;
     @Mock
     private JetpackTaskFragmentOrganizer.TaskFragmentCallback mCallback;
+    @Mock
     private SplitController mSplitController;
+    @Mock
+    private Handler mHandler;
     private JetpackTaskFragmentOrganizer mOrganizer;
 
     @Before
@@ -73,9 +79,8 @@
         MockitoAnnotations.initMocks(this);
         mOrganizer = new JetpackTaskFragmentOrganizer(Runnable::run, mCallback);
         mOrganizer.registerOrganizer();
-        mSplitController = new SplitController();
         spyOn(mOrganizer);
-        spyOn(mSplitController);
+        doReturn(mHandler).when(mSplitController).getHandler();
     }
 
     @Test
@@ -113,7 +118,7 @@
 
     @Test
     public void testExpandTaskFragment() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mSplitController);
         final TaskFragmentInfo info = createMockInfo(container);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 58870a6..179696a 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.START_CANCELED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
@@ -27,15 +28,20 @@
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
 import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 
@@ -73,14 +79,19 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -116,6 +127,8 @@
     private WindowContainerTransaction mTransaction;
     @Mock
     private Handler mHandler;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
 
     private SplitController mSplitController;
     private SplitPresenter mSplitPresenter;
@@ -123,7 +136,9 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mSplitController = new SplitController();
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        mSplitController = new SplitController(mWindowLayoutComponent);
         mSplitPresenter = mSplitController.mPresenter;
         spyOn(mSplitController);
         spyOn(mSplitPresenter);
@@ -138,7 +153,7 @@
 
     @Test
     public void testGetTopActiveContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         // tf1 has no running activity so is not active.
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
                 new Intent(), taskContainer, mSplitController);
@@ -198,6 +213,7 @@
     @Test
     public void testOnTaskFragmentAppearEmptyTimeout() {
         final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
+        doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any());
         mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
 
         verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
@@ -268,6 +284,8 @@
         final SplitContainer splitContainer = mock(SplitContainer.class);
         doReturn(tf).when(splitContainer).getPrimaryContainer();
         doReturn(tf).when(splitContainer).getSecondaryContainer();
+        doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
+        doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
         final List<SplitContainer> splitContainers =
                 mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
         splitContainers.add(splitContainer);
@@ -298,7 +316,7 @@
 
         // Verify if the top active split is updated if both of its containers are not finished.
         doReturn(false).when(mSplitController)
-                        .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
+                .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
 
         mSplitController.updateContainer(mTransaction, tf);
 
@@ -308,7 +326,7 @@
     @Test
     public void testOnStartActivityResultError() {
         final Intent intent = new Intent();
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 intent, taskContainer, mSplitController);
         final SplitController.ActivityStartMonitor monitor =
@@ -608,7 +626,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -624,7 +642,7 @@
 
         assertFalse(result);
         verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
-                anyBoolean());
+                any(), anyBoolean());
     }
 
     @Test
@@ -641,7 +659,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -656,7 +674,7 @@
 
         assertFalse(result);
         verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
-                anyBoolean());
+                any(), anyBoolean());
     }
 
     @Test
@@ -674,7 +692,7 @@
         assertTrue(result);
         verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
                 mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
-                placeholderRule, true /* isPlaceholder */);
+                placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
     }
 
     @Test
@@ -693,14 +711,15 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                splitRule);
+                splitRule,
+                SPLIT_ATTRIBUTES);
         clearInvocations(mSplitController);
         final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
-        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -720,7 +739,8 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                splitRule);
+                splitRule,
+                SPLIT_ATTRIBUTES);
         final Activity launchedActivity = createMockActivity();
         primaryContainer.addPendingAppearedActivity(launchedActivity);
 
@@ -741,7 +761,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
-        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
+        verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
     @Test
@@ -778,7 +798,8 @@
                 primaryContainer,
                 mActivity,
                 secondaryContainer,
-                placeholderRule);
+                placeholderRule,
+                SPLIT_ATTRIBUTES);
         final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
                 false /* isOnReparent */);
 
@@ -1038,15 +1059,16 @@
     @Test
     public void testOnTransactionReady_taskFragmentParentInfoChanged() {
         final TaskFragmentTransaction transaction = new TaskFragmentTransaction();
-        final Configuration taskConfig = new Configuration();
+        final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(Configuration.EMPTY,
+                DEFAULT_DISPLAY, true);
         transaction.addChange(new TaskFragmentTransaction.Change(
                 TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                 .setTaskId(TASK_ID)
-                .setTaskConfiguration(taskConfig));
+                .setTaskFragmentParentInfo(parentInfo));
         mSplitController.onTransactionReady(transaction);
 
         verify(mSplitController).onTaskFragmentParentInfoChanged(any(), eq(TASK_ID),
-                eq(taskConfig));
+                eq(parentInfo));
         verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(),
                 anyInt(), anyBoolean());
     }
@@ -1088,6 +1110,47 @@
                 anyInt(), anyBoolean());
     }
 
+    @Test
+    public void testHasSamePresentation() {
+        SplitPairRule splitRule1 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+        SplitPairRule splitRule2 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+
+        assertTrue("Rules must have same presentation if tags are null and has same properties.",
+                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                        new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+        splitRule2 = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> true)
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .setTag(TEST_TAG)
+                .build();
+
+        assertFalse("Rules must have different presentations if tags are not equal regardless"
+                        + "of other properties",
+                SplitController.haveSamePresentation(splitRule1, splitRule2,
+                        new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
+
+
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
@@ -1097,6 +1160,7 @@
         doReturn(activity).when(mSplitController).getActivity(activityToken);
         doReturn(TASK_ID).when(activity).getTaskId();
         doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
         return activity;
     }
 
@@ -1135,7 +1199,7 @@
     private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
         final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
                 primaryActivity::equals, i -> false, w -> true)
-                .setSplitRatio(SPLIT_RATIO)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .build();
         mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
     }
@@ -1188,7 +1252,8 @@
                 primaryContainer,
                 primaryContainer.getTopNonFinishingActivity(),
                 secondaryContainer,
-                rule);
+                rule,
+                SPLIT_ATTRIBUTES);
 
         // We need to set those in case we are not respecting clear top.
         // TODO(b/231845476) we should always respect clearTop.
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 25f0e25..6dae0a1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -16,23 +16,28 @@
 
 package androidx.window.extensions.embedding;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.Display.DEFAULT_DISPLAY;
 
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
+import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
 import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
-import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
 import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
-import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -66,6 +71,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -73,6 +80,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+
 /**
  * Test class for {@link SplitPresenter}.
  *
@@ -94,13 +103,17 @@
     private TaskFragmentInfo mTaskFragmentInfo;
     @Mock
     private WindowContainerTransaction mTransaction;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
     private SplitController mController;
     private SplitPresenter mPresenter;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mController = new SplitController();
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        mController = new SplitController(mWindowLayoutComponent);
         mPresenter = mController.mPresenter;
         spyOn(mController);
         spyOn(mPresenter);
@@ -162,59 +175,263 @@
 
     @Test
     public void testShouldShowSideBySide() {
-        Activity secondaryActivity = createMockActivity();
-        final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+        assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES));
 
-        assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule));
+        final SplitAttributes expandContainers = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+                .build();
 
-        // Set minDimensions of primary container to larger than primary bounds.
-        final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
-        Pair<Size, Size> minDimensionsPair = new Pair<>(
-                new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
-
-        assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair));
+        assertFalse(SplitPresenter.shouldShowSplit(expandContainers));
     }
 
     @Test
-    public void testGetBoundsForPosition() {
-        Activity secondaryActivity = createMockActivity();
-        final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
-        final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
-        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
+    public void testGetBoundsForPosition_expandContainers() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
+                .build();
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_splitVertically() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                false /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                false /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
 
         assertEquals("Primary bounds must be reported.",
                 primaryBounds,
-                getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
 
         assertEquals("Secondary bounds must be reported.",
                 secondaryBounds,
-                getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
         assertEquals("Task bounds must be reported.",
                 new Rect(),
-                getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule,
-                        mActivity, null /* miniDimensionsPair */));
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
 
-        Pair<Size, Size> minDimensionsPair = new Pair<>(
-                new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT)
+                .build();
 
-        assertEquals("Fullscreen bounds must be reported because of min dimensions.",
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
                 new Rect(),
-                getBoundsForPosition(POSITION_START, TASK_BOUNDS,
-                        splitRule, mActivity, minDimensionsPair));
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+                .build();
+        // Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE.
+        taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_splitHorizontally() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                true /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                true /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+                .build();
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
+                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
+                .build();
+
+        assertEquals("Secondary bounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Primary bounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_useHingeFallback() {
+        final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
+                false /* splitHorizontally */);
+        final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
+                false /* splitHorizontally */);
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        SplitAttributes.SplitType.RatioSplitType.splitEqually()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
+
+        // There's no hinge on the device. Use fallback SplitType.
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        // Hinge is reported, but the host task is in multi-window mode. Still use fallback
+        // splitType.
+        doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        taskProperties.getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+
+        // Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match
+        // folding area orientation. Still use fallback splitType.
+        doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        taskProperties.getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_fallbackToExpandContainers() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        new SplitAttributes.SplitType.ExpandContainersSplitType()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
+                .build();
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
+    }
+
+    @Test
+    public void testGetBoundsForPosition_useHingeSplitType() {
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(new SplitAttributes.SplitType.HingeSplitType(
+                        new SplitAttributes.SplitType.ExpandContainersSplitType()
+                )).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+                .build();
+        final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo();
+        doReturn(windowLayoutInfo).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds();
+        final Rect primaryBounds = new Rect(
+                TASK_BOUNDS.left,
+                TASK_BOUNDS.top,
+                TASK_BOUNDS.right,
+                hingeBounds.top
+        );
+        final Rect secondaryBounds = new Rect(
+                TASK_BOUNDS.left,
+                hingeBounds.bottom,
+                TASK_BOUNDS.right,
+                TASK_BOUNDS.bottom
+        );
+
+        assertEquals("PrimaryBounds must be reported.",
+                primaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
+
+        assertEquals("SecondaryBounds must be reported.",
+                secondaryBounds,
+                mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
+        assertEquals("Task bounds must be reported.",
+                new Rect(),
+                mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
     }
 
     @Test
     public void testExpandSplitContainerIfNeeded() {
-        SplitContainer splitContainer = mock(SplitContainer.class);
         Activity secondaryActivity = createMockActivity();
         SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
         TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
         TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
-        doReturn(splitRule).when(splitContainer).getSplitRule();
-        doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
-        doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
+        SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity,
+                secondaryTf, splitRule, SPLIT_ATTRIBUTES);
 
         assertThrows(IllegalArgumentException.class, () ->
                 mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
@@ -224,11 +441,13 @@
                 splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
         verify(mPresenter, never()).expandTaskFragment(any(), any());
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
         assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
                 mTransaction, splitContainer, mActivity, secondaryActivity,
                 null /* secondaryIntent */));
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
         secondaryTf.setInfo(mTransaction,
                 createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -238,6 +457,7 @@
         verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
         verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
 
+        splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
         clearInvocations(mPresenter);
 
         assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -256,6 +476,7 @@
         final SplitPairRule rule = new SplitPairRule.Builder(pair ->
                 pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
                 metrics -> true)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .setShouldClearTop(false)
                 .build();
 
@@ -268,6 +489,49 @@
         assertTrue(secondaryTf.isAbove(primaryTf));
     }
 
+    @Test
+    public void testComputeSplitAttributes() {
+        final SplitPairRule splitPairRule = new SplitPairRule.Builder(
+                activityPair -> true,
+                activityIntentPair -> true,
+                windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
+                .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
+                .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+                .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
+                .build();
+        final TaskContainer.TaskProperties taskProperties = getTaskProperty();
+
+        assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+
+        final Pair<Size, Size> minDimensionsPair = new Pair<>(
+                new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
+
+        assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, minDimensionsPair));
+
+        taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
+                TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
+                TASK_BOUNDS.bottom + 1));
+
+        assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(
+                        new SplitAttributes.SplitType.HingeSplitType(
+                                SplitAttributes.SplitType.RatioSplitType.splitEqually()
+                        )
+                ).build();
+
+        mController.setSplitAttributesCalculator(params -> {
+            return splitAttributes;
+        });
+
+        assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
+                splitPairRule, null /* minDimensionsPair */));
+    }
+
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
         final Configuration activityConfig = new Configuration();
@@ -279,4 +543,10 @@
         doReturn(mock(IBinder.class)).when(activity).getActivityToken();
         return activity;
     }
+
+    private static TaskContainer.TaskProperties getTaskProperty() {
+        final Configuration configuration = new Configuration();
+        configuration.windowConfiguration.setBounds(TASK_BOUNDS);
+        return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index dd67e48..af9c6ba 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -21,9 +21,10 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,8 +35,10 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.window.TaskFragmentParentInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -66,7 +69,7 @@
 
     @Test
     public void testIsTaskBoundsInitialized() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertFalse(taskContainer.isTaskBoundsInitialized());
 
@@ -77,7 +80,7 @@
 
     @Test
     public void testSetTaskBounds() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertFalse(taskContainer.setTaskBounds(new Rect()));
 
@@ -87,30 +90,24 @@
     }
 
     @Test
-    public void testIsWindowingModeInitialized() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
-
-        assertFalse(taskContainer.isWindowingModeInitialized());
-
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
-        assertTrue(taskContainer.isWindowingModeInitialized());
-    }
-
-    @Test
     public void testGetWindowingModeForSplitTaskFragment() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final Rect splitBounds = new Rect(0, 0, 500, 1000);
+        final Configuration configuration = new Configuration();
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertEquals(WINDOWING_MODE_MULTI_WINDOW,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertEquals(WINDOWING_MODE_FREEFORM,
                 taskContainer.getWindowingModeForSplitTaskFragment(splitBounds));
@@ -123,22 +120,27 @@
 
     @Test
     public void testIsInPictureInPicture() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final Configuration configuration = new Configuration();
 
         assertFalse(taskContainer.isInPictureInPicture());
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertFalse(taskContainer.isInPictureInPicture());
 
-        taskContainer.setWindowingMode(WINDOWING_MODE_PINNED);
+        configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+        taskContainer.updateTaskFragmentParentInfo(new TaskFragmentParentInfo(configuration,
+                DEFAULT_DISPLAY, true /* visible */));
 
         assertTrue(taskContainer.isInPictureInPicture());
     }
 
     @Test
     public void testIsEmpty() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         assertTrue(taskContainer.isEmpty());
 
@@ -155,7 +157,7 @@
 
     @Test
     public void testGetTopTaskFragmentContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         assertNull(taskContainer.getTopTaskFragmentContainer());
 
         final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
@@ -169,7 +171,7 @@
 
     @Test
     public void testGetTopNonFinishingActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         assertNull(taskContainer.getTopNonFinishingActivity());
 
         final TaskFragmentContainer tf0 = mock(TaskFragmentContainer.class);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 082774e..73428a2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -16,8 +16,8 @@
 
 package androidx.window.extensions.embedding;
 
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -90,7 +90,7 @@
 
     @Test
     public void testNewContainer() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
 
         // One of the activity and the intent must be non-null
         assertThrows(IllegalArgumentException.class,
@@ -103,7 +103,7 @@
 
     @Test
     public void testFinish() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
         doReturn(container).when(mController).getContainerWithActivity(mActivity);
@@ -136,7 +136,7 @@
 
     @Test
     public void testFinish_notFinishActivityThatIsReparenting() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
         final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
@@ -157,7 +157,7 @@
 
     @Test
     public void testSetInfo() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         // Pending activity should be cleared when it has appeared on server side.
         final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
                 null /* pendingAppearedIntent */, taskContainer, mController);
@@ -185,7 +185,7 @@
 
     @Test
     public void testIsWaitingActivityAppear() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
 
@@ -207,7 +207,7 @@
     @Test
     public void testAppearEmptyTimeout() {
         doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
 
@@ -247,7 +247,7 @@
 
     @Test
     public void testCollectNonFinishingActivities() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         List<Activity> activities = container.collectNonFinishingActivities();
@@ -275,7 +275,7 @@
 
     @Test
     public void testAddPendingActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
@@ -289,7 +289,7 @@
 
     @Test
     public void testIsAbove() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
@@ -301,7 +301,7 @@
 
     @Test
     public void testGetBottomMostActivity() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
@@ -318,7 +318,7 @@
 
     @Test
     public void testOnActivityDestroyed() {
-        final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+        final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
                 mIntent, taskContainer, mController);
         container.addPendingAppearedActivity(mActivity);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 9230c22..ca977ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -179,6 +179,14 @@
     }
 
     /**
+     * Returns the {@link DisplayAreaInfo} of the {@link DisplayAreaInfo#displayId}.
+     */
+    @Nullable
+    public DisplayAreaInfo getDisplayAreaInfo(int displayId) {
+        return mDisplayAreasInfo.get(displayId);
+    }
+
+    /**
      * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
      * {@link DisplayAreaInfo#displayId}.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index d88cc00..d150261 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
 import android.animation.Animator;
 import android.animation.ValueAnimator;
@@ -129,11 +130,19 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        boolean isChangeTransition = false;
         for (TransitionInfo.Change change : info.getChanges()) {
-            if (change.getMode() == TRANSIT_CHANGE
-                    && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
-                return createChangeAnimationAdapters(info, startTransaction);
+            if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
+                // Skip the animation if the windows are behind an app starting window.
+                return new ArrayList<>();
             }
+            if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE
+                    && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+                isChangeTransition = true;
+            }
+        }
+        if (isChangeTransition) {
+            return createChangeAnimationAdapters(info, startTransaction);
         }
         if (Transitions.isClosingType(info.getType())) {
             return createCloseAnimationAdapters(info);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 27d3e35..35e88e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,7 +27,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -599,13 +598,13 @@
     static Optional<DesktopModeController> provideDesktopModeController(
             Context context, ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @ShellMainThread Handler mainHandler,
             Transitions transitions
     ) {
         if (DesktopMode.IS_SUPPORTED) {
             return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
-                    rootDisplayAreaOrganizer, mainHandler, transitions));
+                    rootTaskDisplayAreaOrganizer, mainHandler, transitions));
         } else {
             return Optional.empty();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index c07ce10..6e44d58 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -22,19 +22,21 @@
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
 
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellInit;
@@ -47,18 +49,18 @@
 
     private final Context mContext;
     private final ShellTaskOrganizer mShellTaskOrganizer;
-    private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     private final SettingsObserver mSettingsObserver;
     private final Transitions mTransitions;
 
     public DesktopModeController(Context context, ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
-            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @ShellMainThread Handler mainHandler,
             Transitions transitions) {
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
-        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
         mTransitions = transitions;
         shellInit.addInitCallback(this::onInit, this);
@@ -92,15 +94,32 @@
             wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
                     true /* transfer */);
         }
-        wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
-                targetWindowingMode), true /* transfer */);
+        prepareWindowingModeChange(wct, displayId, targetWindowingMode);
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
         } else {
-            mRootDisplayAreaOrganizer.applyTransaction(wct);
+            mRootTaskDisplayAreaOrganizer.applyTransaction(wct);
         }
     }
 
+    private void prepareWindowingModeChange(WindowContainerTransaction wct,
+            int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
+        DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
+                .getDisplayAreaInfo(displayId);
+        if (displayAreaInfo == null) {
+            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+                    "unable to update windowing mode for display %d display not found", displayId);
+            return;
+        }
+
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+                displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+                windowingMode);
+
+        wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+    }
+
     /**
      * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 3758471..991f136 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -133,6 +133,20 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
 
+    public static final int ENTER_REASON_UNKNOWN = 0;
+    public static final int ENTER_REASON_MULTI_INSTANCE = 1;
+    public static final int ENTER_REASON_DRAG = 2;
+    public static final int ENTER_REASON_LAUNCHER = 3;
+    /** Acts as a mapping to the actual EnterReasons as defined in the logging proto */
+    @IntDef(value = {
+            ENTER_REASON_MULTI_INSTANCE,
+            ENTER_REASON_DRAG,
+            ENTER_REASON_LAUNCHER,
+            ENTER_REASON_UNKNOWN
+    })
+    public @interface SplitEnterReason {
+    }
+
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellController mShellController;
     private final ShellTaskOrganizer mTaskOrganizer;
@@ -394,7 +408,7 @@
      */
     public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             @Nullable Bundle options, UserHandle user, @NonNull InstanceId instanceId) {
-        mStageCoordinator.getLogger().enterRequested(instanceId);
+        mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
         startShortcut(packageName, shortcutId, position, options, user);
     }
 
@@ -442,7 +456,7 @@
      */
     public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
             @SplitPosition int position, @Nullable Bundle options, @NonNull InstanceId instanceId) {
-        mStageCoordinator.getLogger().enterRequested(instanceId);
+        mStageCoordinator.getLogger().enterRequested(instanceId, ENTER_REASON_LAUNCHER);
         startIntent(intent, fillInIntent, position, options);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 626ccb1..2dc4a04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -17,6 +17,8 @@
 package com.android.wm.shell.splitscreen;
 
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
@@ -28,6 +30,10 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_DRAG;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_UNKNOWN;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
@@ -38,6 +44,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 
+import android.annotation.Nullable;
 import android.util.Slog;
 
 import com.android.internal.logging.InstanceId;
@@ -59,7 +66,7 @@
 
     // Drag info
     private @SplitPosition int mDragEnterPosition;
-    private InstanceId mEnterSessionId;
+    private @Nullable InstanceId mEnterSessionId;
 
     // For deduping async events
     private int mLastMainStagePosition = -1;
@@ -67,6 +74,7 @@
     private int mLastSideStagePosition = -1;
     private int mLastSideStageUid = -1;
     private float mLastSplitRatio = -1f;
+    private @SplitScreenController.SplitEnterReason int mEnterReason = ENTER_REASON_UNKNOWN;
 
     public SplitscreenEventLogger() {
         mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
@@ -79,20 +87,35 @@
         return mLoggerSessionId != null;
     }
 
+    public boolean isEnterRequestedByDrag() {
+        return mEnterReason == ENTER_REASON_DRAG;
+    }
+
     /**
      * May be called before logEnter() to indicate that the session was started from a drag.
      */
     public void enterRequestedByDrag(@SplitPosition int position, InstanceId enterSessionId) {
         mDragEnterPosition = position;
-        enterRequested(enterSessionId);
+        enterRequested(enterSessionId, ENTER_REASON_DRAG);
     }
 
     /**
      * May be called before logEnter() to indicate that the session was started from launcher.
      * This specifically is for all the scenarios where split started without a drag interaction
      */
-    public void enterRequested(InstanceId enterSessionId) {
+    public void enterRequested(@Nullable InstanceId enterSessionId,
+            @SplitScreenController.SplitEnterReason int enterReason) {
         mEnterSessionId = enterSessionId;
+        mEnterReason = enterReason;
+    }
+
+    /**
+     * @return if an enterSessionId has been set via either
+     *         {@link #enterRequested(InstanceId, int)} or
+     *         {@link #enterRequestedByDrag(int, InstanceId)}
+     */
+    public boolean hasValidEnterSessionId() {
+        return mEnterSessionId != null;
     }
 
     /**
@@ -103,9 +126,7 @@
             @SplitPosition int sideStagePosition, int sideStageUid,
             boolean isLandscape) {
         mLoggerSessionId = mIdSequence.newInstanceId();
-        int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
-                ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
-                : SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+        int enterReason = getLoggerEnterReason(isLandscape);
         updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
                 mainStageUid);
         updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
@@ -124,6 +145,20 @@
                 mLoggerSessionId.getId());
     }
 
+    private int getLoggerEnterReason(boolean isLandscape) {
+        switch (mEnterReason) {
+            case ENTER_REASON_MULTI_INSTANCE:
+                return SPLITSCREEN_UICHANGED__ENTER_REASON__MULTI_INSTANCE;
+            case ENTER_REASON_LAUNCHER:
+                return SPLITSCREEN_UICHANGED__ENTER_REASON__LAUNCHER;
+            case ENTER_REASON_DRAG:
+                return getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape);
+            case ENTER_REASON_UNKNOWN:
+            default:
+                return SPLITSCREEN_UICHANGED__ENTER_REASON__UNKNOWN_ENTER;
+        }
+    }
+
     /**
      * Returns the framework logging constant given a splitscreen exit reason.
      */
@@ -189,6 +224,7 @@
         mLastMainStageUid = -1;
         mLastSideStagePosition = -1;
         mLastSideStageUid = -1;
+        mEnterReason = ENTER_REASON_UNKNOWN;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 25793ed..c17f822 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
@@ -44,6 +46,8 @@
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -501,6 +505,12 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
 
+        // If split still not active, apply windows bounds first to avoid surface reset to
+        // wrong pos by SurfaceAnimator from wms.
+        if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+            updateWindowBounds(mSplitLayout, wct);
+        }
+
         wct.sendPendingIntent(intent, fillInIntent, options);
         mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
     }
@@ -669,7 +679,7 @@
 
     private void setEnterInstanceId(InstanceId instanceId) {
         if (instanceId != null) {
-            mLogger.enterRequested(instanceId);
+            mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
         }
     }
 
@@ -1107,6 +1117,10 @@
 
     private void addActivityOptions(Bundle opts, StageTaskListener stage) {
         opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+        // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
+        // will be canceled.
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
     }
 
     void updateActivityOptions(Bundle opts, @SplitPosition int position) {
@@ -1453,18 +1467,27 @@
                 }
             }
         } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
-            // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
-            onSplitScreenEnter();
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             mSplitLayout.init();
-            mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
-            mMainStage.activate(wct, true /* includingTopTask */);
-            updateWindowBounds(mSplitLayout, wct);
-            wct.reorder(mRootTaskInfo.token, true);
-            wct.setForceTranslucent(mRootTaskInfo.token, false);
+            if (mLogger.isEnterRequestedByDrag()) {
+                prepareEnterSplitScreen(wct);
+            } else {
+                // TODO (b/238697912) : Add the validation to prevent entering non-recovered status
+                onSplitScreenEnter();
+                mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+                mMainStage.activate(wct, true /* includingTopTask */);
+                updateWindowBounds(mSplitLayout, wct);
+                wct.reorder(mRootTaskInfo.token, true);
+                wct.setForceTranslucent(mRootTaskInfo.token, false);
+            }
+
             mSyncQueue.queue(wct);
             mSyncQueue.runInSync(t -> {
-                mSplitLayout.flingDividerToCenter();
+                if (mLogger.isEnterRequestedByDrag()) {
+                    updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+                } else {
+                    mSplitLayout.flingDividerToCenter();
+                }
             });
         }
         if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
@@ -1472,6 +1495,9 @@
             updateRecentTasksSplitPair();
 
             if (!mLogger.hasStartedSession()) {
+                if (!mLogger.hasValidEnterSessionId()) {
+                    mLogger.enterRequested(null /*enterSessionId*/, ENTER_REASON_MULTI_INSTANCE);
+                }
                 mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
                         getMainStagePosition(), mMainStage.getTopChildTaskUid(),
                         getSideStagePosition(), mSideStage.getTopChildTaskUid(),
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 3ca5b9c..d6adaa7 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -48,6 +48,6 @@
         "wm-flicker-common-assertions",
         "wm-flicker-common-app-helpers",
         "platform-test-annotations",
-        "wmshell-flicker-test-components",
+        "flickertestapplib",
     ],
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 574a9f4..1284c41 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -19,7 +19,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
         <option name="test-file-name" value="WMShellFlickerTests.apk"/>
-        <option name="test-file-name" value="WMShellFlickerTestApp.apk" />
+        <option name="test-file-name" value="FlickerTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.wm.shell.flicker"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
new file mode 100644
index 0000000..c045325
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/MultiWindowUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker
+
+import android.app.Instrumentation
+import android.content.Context
+import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
+import java.io.IOException
+
+object MultiWindowUtils {
+    private fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+        try {
+            SystemUtil.runShellCommand(instrumentation, cmd)
+        } catch (e: IOException) {
+            Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+        }
+    }
+
+    fun getDevEnableNonResizableMultiWindow(context: Context): Int =
+        Settings.Global.getInt(context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
+
+    fun setDevEnableNonResizableMultiWindow(context: Context, configValue: Int) =
+        Settings.Global.putInt(context.contentResolver,
+            Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW, configValue)
+
+    fun setSupportsNonResizableMultiWindow(instrumentation: Instrumentation, configValue: Int) =
+        executeShellCommand(
+            instrumentation,
+            createConfigSupportsNonResizableMultiWindowCommand(configValue))
+
+    fun resetMultiWindowConfig(instrumentation: Instrumentation) =
+        executeShellCommand(instrumentation, resetMultiWindowConfigCommand)
+
+    private fun createConfigSupportsNonResizableMultiWindowCommand(configValue: Int): String =
+        "wm set-multi-window-config --supportsNonResizable $configValue"
+
+    private const val resetMultiWindowConfigCommand: String = "wm reset-multi-window-config"
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 1390334..cbe085b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -19,6 +19,7 @@
 import android.app.INotificationManager
 import android.app.NotificationManager
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.ServiceManager
 import android.view.Surface
 import androidx.test.uiautomator.By
@@ -28,9 +29,9 @@
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
 import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.LaunchBubbleHelper
 import org.junit.runners.Parameterized
 
 /**
@@ -47,7 +48,7 @@
             ServiceManager.getService(Context.NOTIFICATION_SERVICE))
 
     private val uid = context.packageManager.getApplicationInfo(
-            testApp.`package`, 0).uid
+            testApp.`package`, PackageManager.ApplicationInfoFlags.of(0)).uid
 
     @JvmOverloads
     protected open fun buildTransition(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
deleted file mode 100644
index 01ba990..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.util.Log
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiObject2
-import androidx.test.uiautomator.Until
-import com.android.compatibility.common.util.SystemUtil
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import java.io.IOException
-
-abstract class BaseAppHelper(
-    instrumentation: Instrumentation,
-    launcherName: String,
-    component: IComponentNameMatcher
-) : StandardAppHelper(
-    instrumentation,
-    launcherName,
-    component,
-    LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
-) {
-    private val appSelector = By.pkg(`package`).depth(0)
-
-    protected val isTelevision: Boolean
-        get() = context.packageManager.run {
-            hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
-        }
-
-    val ui: UiObject2?
-        get() = uiDevice.findObject(appSelector)
-
-    fun waitUntilClosed(): Boolean {
-        return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
-    }
-
-    companion object {
-        private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
-
-        fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
-            try {
-                SystemUtil.runShellCommand(instrumentation, cmd)
-            } catch (e: IOException) {
-                Log.e("BaseAppHelper", "executeShellCommand error! $e")
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
deleted file mode 100644
index 471e010..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FixedAppHelper.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class FixedAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.FixedActivity.LABEL,
-    Components.FixedActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
deleted file mode 100644
index 2e690de..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.testapp.Components
-
-open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.ImeActivity.LABEL,
-    Components.ImeActivity.COMPONENT.toFlickerComponent()
-) {
-    /**
-     * Opens the IME and wait for it to be displayed
-     *
-     * @param wmHelper Helper used to wait for WindowManager states
-     */
-    open fun openIME(wmHelper: WindowManagerStateHelper) {
-        if (!isTelevision) {
-            val editText = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "plain_text_input")),
-                FIND_TIMEOUT)
-
-            require(editText != null) {
-                "Text field not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
-            }
-            editText.click()
-            wmHelper.StateSyncBuilder()
-                .withImeShown()
-                .waitForAndVerify()
-        } else {
-            // If we do the same thing as above - editText.click() - on TV, that's going to force TV
-            // into the touch mode. We really don't want that.
-            launchViaIntent(action = Components.ImeActivity.ACTION_OPEN_IME)
-        }
-    }
-
-    /**
-     * Opens the IME and wait for it to be gone
-     *
-     * @param wmHelper Helper used to wait for WindowManager states
-     */
-    open fun closeIME(wmHelper: WindowManagerStateHelper) {
-        if (!isTelevision) {
-            uiDevice.pressBack()
-            // Using only the AccessibilityInfo it is not possible to identify if the IME is active
-            wmHelper.StateSyncBuilder()
-                .withImeGone()
-                .waitForAndVerify()
-        } else {
-            // While pressing the back button should close the IME on TV as well, it may also lead
-            // to the app closing. So let's instead just ask the app to close the IME.
-            launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
deleted file mode 100644
index 1b8a44b..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/LaunchBubbleHelper.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
-
-class LaunchBubbleHelper(instrumentation: Instrumentation) : BaseAppHelper(
-    instrumentation,
-    Components.LaunchBubbleActivity.LABEL,
-    Components.LaunchBubbleActivity.COMPONENT.toFlickerComponent()
-) {
-
-    companion object {
-        const val TIMEOUT_MS = 3_000L
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
deleted file mode 100644
index 52e5d7e..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.graphics.Point
-import android.os.SystemClock
-import android.view.InputDevice
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.traces.common.IComponentMatcher
-import com.android.server.wm.traces.common.IComponentNameMatcher
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.SPLIT_DECOR_MANAGER
-import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
-
-class SplitScreenHelper(
-    instrumentation: Instrumentation,
-    activityLabel: String,
-    componentInfo: IComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentInfo) {
-
-    companion object {
-        const val TIMEOUT_MS = 3_000L
-        const val DRAG_DURATION_MS = 1_000L
-        const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
-        const val DIVIDER_BAR = "docked_divider_handle"
-        const val GESTURE_STEP_MS = 16L
-        const val LONG_PRESS_TIME_MS = 100L
-
-        private val notificationScrollerSelector: BySelector
-            get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
-        private val notificationContentSelector: BySelector
-            get() = By.text("Notification content")
-        private val dividerBarSelector: BySelector
-            get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
-
-        fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SplitScreenActivity.LABEL,
-                Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SplitScreenSecondaryActivity.LABEL,
-                Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.NonResizeableActivity.LABEL,
-                Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.SendNotificationActivity.LABEL,
-                Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun getIme(instrumentation: Instrumentation): SplitScreenHelper =
-            SplitScreenHelper(
-                instrumentation,
-                Components.ImeActivity.LABEL,
-                Components.ImeActivity.COMPONENT.toFlickerComponent()
-            )
-
-        fun waitForSplitComplete(
-            wmHelper: WindowManagerStateHelper,
-            primaryApp: IComponentMatcher,
-            secondaryApp: IComponentMatcher,
-        ) {
-            wmHelper.StateSyncBuilder()
-                .withWindowSurfaceAppeared(primaryApp)
-                .withWindowSurfaceAppeared(secondaryApp)
-                .withSplitDividerVisible()
-                .waitForAndVerify()
-        }
-
-        fun enterSplit(
-            wmHelper: WindowManagerStateHelper,
-            tapl: LauncherInstrumentation,
-            primaryApp: SplitScreenHelper,
-            secondaryApp: SplitScreenHelper
-        ) {
-            tapl.workspace.switchToOverview().dismissAllTasks()
-            primaryApp.launchViaIntent(wmHelper)
-            secondaryApp.launchViaIntent(wmHelper)
-            tapl.goHome()
-            wmHelper.StateSyncBuilder()
-                .withHomeActivityVisible()
-                .waitForAndVerify()
-            splitFromOverview(tapl)
-            waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
-        }
-
-        fun splitFromOverview(tapl: LauncherInstrumentation) {
-            // Note: The initial split position in landscape is different between tablet and phone.
-            // In landscape, tablet will let the first app split to right side, and phone will
-            // split to left side.
-            if (tapl.isTablet) {
-                tapl.workspace.switchToOverview().overviewActions
-                    .clickSplit()
-                    .currentTask
-                    .open()
-            } else {
-                tapl.workspace.switchToOverview().currentTask
-                    .tapMenu()
-                    .tapSplitMenuItem()
-                    .currentTask
-                    .open()
-            }
-            SystemClock.sleep(TIMEOUT_MS)
-        }
-
-        fun dragFromNotificationToSplit(
-            instrumentation: Instrumentation,
-            device: UiDevice,
-            wmHelper: WindowManagerStateHelper
-        ) {
-            val displayBounds = wmHelper.currentState.layerState
-                .displays.firstOrNull { !it.isVirtual }
-                ?.layerStackSpace
-                ?: error("Display not found")
-
-            // Pull down the notifications
-            device.swipe(
-                displayBounds.centerX(), 5,
-                displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
-            )
-            SystemClock.sleep(TIMEOUT_MS)
-
-            // Find the target notification
-            val notificationScroller = device.wait(
-                Until.findObject(notificationScrollerSelector), TIMEOUT_MS
-            )
-            var notificationContent = notificationScroller.findObject(notificationContentSelector)
-
-            while (notificationContent == null) {
-                device.swipe(
-                    displayBounds.centerX(), displayBounds.centerY(),
-                    displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
-                )
-                notificationContent = notificationScroller.findObject(notificationContentSelector)
-            }
-
-            // Drag to split
-            val dragStart = notificationContent.visibleCenter
-            val dragMiddle = Point(dragStart.x + 50, dragStart.y)
-            val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
-            val downTime = SystemClock.uptimeMillis()
-
-            touch(
-                instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
-                TIMEOUT_MS, dragStart
-            )
-            // It needs a horizontal movement to trigger the drag
-            touchMove(
-                instrumentation, downTime, SystemClock.uptimeMillis(),
-                DRAG_DURATION_MS, dragStart, dragMiddle
-            )
-            touchMove(
-                instrumentation, downTime, SystemClock.uptimeMillis(),
-                DRAG_DURATION_MS, dragMiddle, dragEnd
-            )
-            // Wait for a while to start splitting
-            SystemClock.sleep(TIMEOUT_MS)
-            touch(
-                instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
-                GESTURE_STEP_MS, dragEnd
-            )
-            SystemClock.sleep(TIMEOUT_MS)
-        }
-
-        fun touch(
-            instrumentation: Instrumentation,
-            action: Int,
-            downTime: Long,
-            eventTime: Long,
-            duration: Long,
-            point: Point
-        ) {
-            val motionEvent = MotionEvent.obtain(
-                downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
-            )
-            motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-            instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
-            motionEvent.recycle()
-            SystemClock.sleep(duration)
-        }
-
-        fun touchMove(
-            instrumentation: Instrumentation,
-            downTime: Long,
-            eventTime: Long,
-            duration: Long,
-            from: Point,
-            to: Point
-        ) {
-            val steps: Long = duration / GESTURE_STEP_MS
-            var currentTime = eventTime
-            var currentX = from.x.toFloat()
-            var currentY = from.y.toFloat()
-            val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
-            val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
-
-            for (i in 1..steps) {
-                val motionMove = MotionEvent.obtain(
-                    downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
-                )
-                motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
-                instrumentation.uiAutomation.injectInputEvent(motionMove, true)
-                motionMove.recycle()
-
-                currentTime += GESTURE_STEP_MS
-                if (i == steps - 1) {
-                    currentX = to.x.toFloat()
-                    currentY = to.y.toFloat()
-                } else {
-                    currentX += stepX
-                    currentY += stepY
-                }
-                SystemClock.sleep(GESTURE_STEP_MS)
-            }
-        }
-
-        fun longPress(
-            instrumentation: Instrumentation,
-            point: Point
-        ) {
-            val downTime = SystemClock.uptimeMillis()
-            touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
-            SystemClock.sleep(LONG_PRESS_TIME_MS)
-            touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
-        }
-
-        fun createShortcutOnHotseatIfNotExist(
-            tapl: LauncherInstrumentation,
-            appName: String
-        ) {
-            tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
-            val allApps = tapl.workspace.switchToAllApps()
-            allApps.freeze()
-            try {
-                allApps.getAppIcon(appName).dragToHotseat(0)
-            } finally {
-                allApps.unfreeze()
-            }
-        }
-
-        fun dragDividerToResizeAndWait(
-            device: UiDevice,
-            wmHelper: WindowManagerStateHelper
-        ) {
-            val displayBounds = wmHelper.currentState.layerState
-                .displays.firstOrNull { !it.isVirtual }
-                ?.layerStackSpace
-                ?: error("Display not found")
-            val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-            dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
-
-            wmHelper.StateSyncBuilder()
-                .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
-                .waitForAndVerify()
-        }
-
-        fun dragDividerToDismissSplit(
-            device: UiDevice,
-            wmHelper: WindowManagerStateHelper,
-            dragToRight: Boolean,
-            dragToBottom: Boolean
-        ) {
-            val displayBounds = wmHelper.currentState.layerState
-                .displays.firstOrNull { !it.isVirtual }
-                ?.layerStackSpace
-                ?: error("Display not found")
-            val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-            dividerBar.drag(Point(
-                if (dragToRight) {
-                    displayBounds.width * 4 / 5
-                } else {
-                    displayBounds.width * 1 / 5
-                },
-                if (dragToBottom) {
-                    displayBounds.height * 4 / 5
-                } else {
-                    displayBounds.height * 1 / 5
-                }))
-        }
-
-        fun doubleTapDividerToSwitch(device: UiDevice) {
-            val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
-            val interval = (ViewConfiguration.getDoubleTapTimeout() +
-                ViewConfiguration.getDoubleTapMinTime()) / 2
-            dividerBar.click()
-            SystemClock.sleep(interval.toLong())
-            dividerBar.click()
-        }
-
-        fun copyContentInSplit(
-            instrumentation: Instrumentation,
-            device: UiDevice,
-            sourceApp: IComponentNameMatcher,
-            destinationApp: IComponentNameMatcher,
-        ) {
-            // Copy text from sourceApp
-            val textView = device.wait(Until.findObject(
-                By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
-            longPress(instrumentation, textView.getVisibleCenter())
-
-            val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
-            copyBtn.click()
-
-            // Paste text to destinationApp
-            val editText = device.wait(Until.findObject(
-                By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
-            longPress(instrumentation, editText.getVisibleCenter())
-
-            val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
-            pasteBtn.click()
-
-            // Verify text
-            if (!textView.getText().contentEquals(editText.getText())) {
-                error("Fail to copy content in split")
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
deleted file mode 100644
index f9b0800..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/CommonAssertions.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-@file:JvmName("CommonAssertions")
-package com.android.wm.shell.flicker.pip
-
-internal const val PIP_WINDOW_COMPONENT = "PipMenuActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 87d800c..4788507 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -28,16 +28,17 @@
 import com.android.server.wm.flicker.annotation.Group3
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -72,7 +73,7 @@
 class EnterPipToOtherOrientationTest(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
-    private val testApp = FixedAppHelper(instrumentation)
+    private val testApp = FixedOrientationAppHelper(instrumentation)
     private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
     private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
 
@@ -127,7 +128,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] has the correct position at
+     * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
      * the start and end of the transition
      */
     @FlakyTest
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 45851c8..6285991 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -18,14 +18,14 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import org.junit.Test
 
 /**
  * Base class for pip expand tests
  */
 abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    protected val testApp = FixedAppHelper(instrumentation)
+    protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /**
      * Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index 19bdca5..5f94196 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -18,9 +18,9 @@
 
 import android.platform.test.annotations.Presubmit
 import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.wm.shell.flicker.Direction
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Test
 
 /**
@@ -29,7 +29,7 @@
 abstract class MovePipShelfHeightTransition(
     testSpec: FlickerTestParameter
 ) : PipTransition(testSpec) {
-    protected val testApp = FixedAppHelper(instrumentation)
+    protected val testApp = FixedOrientationAppHelper(instrumentation)
 
     /**
      * Checks [pipApp] window remains visible throughout the animation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3d3b53d..2aa0da9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -24,11 +24,11 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.traces.common.ComponentNameMatcher
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index f13698f..0fce64e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -25,10 +25,10 @@
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.annotation.Group4
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -62,7 +62,7 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group4
 open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
-    private val fixedApp = FixedAppHelper(instrumentation)
+    private val testApp = SimpleAppHelper(instrumentation)
     private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
     private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
 
@@ -74,7 +74,7 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = buildTransition {
             setup {
-                fixedApp.launchViaIntent(wmHelper)
+                testApp.launchViaIntent(wmHelper)
                 setRotation(testSpec.startRotation)
             }
             transitions {
@@ -90,48 +90,48 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
+     * Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition
      */
     @Presubmit
     @Test
     fun fixedAppLayer_StartingBounds() {
         testSpec.assertLayersStart {
-            visibleRegion(fixedApp).coversAtMost(screenBoundsStart)
+            visibleRegion(testApp).coversAtMost(screenBoundsStart)
         }
     }
 
     /**
-     * Checks that [fixedApp] layer is within [screenBoundsEnd] at the end of the transition
+     * Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition
      */
     @Presubmit
     @Test
     fun fixedAppLayer_EndingBounds() {
         testSpec.assertLayersEnd {
-            visibleRegion(fixedApp).coversAtMost(screenBoundsEnd)
+            visibleRegion(testApp).coversAtMost(screenBoundsEnd)
         }
     }
 
     /**
-     * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
+     * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the start
      * of the transition
      */
     @Presubmit
     @Test
     fun appLayers_StartingBounds() {
         testSpec.assertLayersStart {
-            visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsStart)
+            visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
         }
     }
 
     /**
-     * Checks that [fixedApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
+     * Checks that [testApp] plus [pipApp] layers are within [screenBoundsEnd] at the end
      * of the transition
      */
     @Presubmit
     @Test
     fun appLayers_EndingBounds() {
         testSpec.assertLayersEnd {
-            visibleRegion(fixedApp.or(pipApp)).coversExactly(screenBoundsEnd)
+            visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
         }
     }
 
@@ -165,26 +165,26 @@
     }
 
     /**
-     * Ensure that the [pipApp] window does not obscure the [fixedApp] at the start of the
+     * Ensure that the [pipApp] window does not obscure the [testApp] at the start of the
      * transition
      */
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_Start() {
         testSpec.assertWmStart {
-            isAboveWindow(pipApp, fixedApp)
+            isAboveWindow(pipApp, testApp)
         }
     }
 
     /**
-     * Ensure that the [pipApp] window does not obscure the [fixedApp] at the end of the
+     * Ensure that the [pipApp] window does not obscure the [testApp] at the end of the
      * transition
      */
     @Presubmit
     @Test
     fun pipIsAboveFixedAppWindow_End() {
         testSpec.assertWmEnd {
-            isAboveWindow(pipApp, fixedApp)
+            isAboveWindow(pipApp, testApp)
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index b67cf77..ff505a0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -21,12 +21,12 @@
 import android.view.Surface
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.PipAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.testapp.Components
 
 abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected val pipApp = PipAppHelper(instrumentation)
@@ -61,11 +61,11 @@
      *
      * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test)
      * @param stringExtras Arguments to pass to the PIP launch intent
-     * @param extraSpec Addicional segment of flicker specification
+     * @param extraSpec Additional segment of flicker specification
      */
     @JvmOverloads
     protected open fun buildTransition(
-        stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true"),
+        stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
         extraSpec: FlickerBuilder.() -> Unit = {}
     ): FlickerBuilder.() -> Unit {
         return {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 866e4e8..30332f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -31,9 +31,9 @@
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.testapp.Components
-import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
@@ -68,7 +68,7 @@
                 pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
                     EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
                 // Enter PiP.
-                broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
+                broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
                 // System bar may fade out during fixed rotation.
                 wmHelper.StateSyncBuilder()
                     .withPipShown()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
new file mode 100644
index 0000000..cdd768a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipAppHelperTv.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.wm.shell.flicker.pip.tv
+
+import android.app.Instrumentation
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+
+/**
+ * Helper class for PIP app on AndroidTV
+ */
+open class PipAppHelperTv(instrumentation: Instrumentation) : PipAppHelper(instrumentation) {
+    private val appSelector = By.pkg(`package`).depth(0)
+
+    val ui: UiObject2?
+        get() = uiDevice.findObject(appSelector)
+
+    private fun focusOnObject(selector: BySelector): Boolean {
+        // We expect all the focusable UI elements to be arranged in a way so that it is possible
+        // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
+        // from "the bottom".
+        repeat(FOCUS_ATTEMPTS) {
+            uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
+                ?: error("The object we try to focus on is gone.")
+
+            uiDevice.pressDPadDown()
+            uiDevice.waitForIdle()
+        }
+        return false
+    }
+
+    override fun clickObject(resId: String) {
+        val selector = By.res(`package`, resId)
+        focusOnObject(selector) || error("Could not focus on `$resId` object")
+        uiDevice.pressDPadCenter()
+    }
+
+    @Deprecated(
+        "Use PipAppHelper.closePipWindow(wmHelper) instead",
+        ReplaceWith("closePipWindow(wmHelper)")
+    )
+    override fun closePipWindow() {
+        uiDevice.closeTvPipWindow()
+    }
+
+    /**
+     * Taps the pip window and dismisses it by clicking on the X button.
+     */
+    override fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+        uiDevice.closeTvPipWindow()
+
+        // Wait for animation to complete.
+        wmHelper.StateSyncBuilder()
+            .withPipGone()
+            .withHomeActivityVisible()
+            .waitForAndVerify()
+    }
+
+    fun waitUntilClosed(): Boolean {
+        val appSelector = By.pkg(`package`).depth(0)
+        return uiDevice.wait(Until.gone(appSelector), APP_CLOSE_WAIT_TIME_MS)
+    }
+
+    companion object {
+        private const val FOCUS_ATTEMPTS = 20
+        private const val APP_CLOSE_WAIT_TIME_MS = 3_000L
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 180ced0..a16f5f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -21,7 +21,6 @@
 import android.view.Surface
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.UiDevice
-import com.android.wm.shell.flicker.helpers.PipAppHelper
 import org.junit.Before
 import org.junit.runners.Parameterized
 
@@ -38,7 +37,7 @@
                 hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
         }
     }
-    protected val testApp = PipAppHelper(instrumentation)
+    protected val testApp = PipAppHelperTv(instrumentation)
 
     @Before
     open fun televisionSetUp() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 4be19d6..68dbbfb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -19,8 +19,8 @@
 import android.graphics.Rect
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.UiObject2
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
-import com.android.wm.shell.flicker.testapp.Components
 import com.android.wm.shell.flicker.wait
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
@@ -165,44 +165,44 @@
         enterPip_openMenu_assertShown()
 
         // PiP menu should contain "No-Op", "Off" and "Clear" buttons...
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
                 ?: fail("\"No-Op\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
                 ?: fail("\"Off\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
                 ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... and should also contain the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
         // Invoking the "Off" action should replace it with the "On" action/button and should
         // remove the "No-Op" action/button. "Clear" action/button should remain in the menu ...
-        uiDevice.waitForTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_ON)
+        uiDevice.waitForTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_ON)
                 ?: fail("\"On\" button should be shown in Pip for a corresponding custom action")
         assertNull("\"No-Op\" button should not be shown in Pip menu",
                 uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_NO_OP))
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+                    ActivityOptions.Pip.MENU_ACTION_NO_OP))
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
                         ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... as well as the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
         // Invoking the "Clear" action should remove all the custom actions and their corresponding
         // buttons, ...
         uiDevice.waitUntilTvPipMenuElementWithDescriptionIsGone(
-            Components.PipActivity.MENU_ACTION_ON)?.also {
+            ActivityOptions.Pip.MENU_ACTION_ON)?.also {
             isGone -> if (!isGone) fail("\"On\" button should not be shown in Pip menu")
         }
         assertNull("\"Off\" button should not be shown in Pip menu",
                 uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_OFF))
+                    ActivityOptions.Pip.MENU_ACTION_OFF))
         assertNull("\"Clear\" button should not be shown in Pip menu",
                 uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_CLEAR))
+                    ActivityOptions.Pip.MENU_ACTION_CLEAR))
         assertNull("\"No-Op\" button should not be shown in Pip menu",
                 uiDevice.findTvPipMenuElementWithDescription(
-                    Components.PipActivity.MENU_ACTION_NO_OP))
+                    ActivityOptions.Pip.MENU_ACTION_NO_OP))
         // ... but the menu should still contain the "Full screen" and "Close" buttons.
         assertFullscreenAndCloseButtonsAreShown()
 
@@ -217,11 +217,11 @@
         enterPip_openMenu_assertShown()
 
         // PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_NO_OP)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_NO_OP)
                 ?: fail("\"No-Op\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_OFF)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_OFF)
                 ?: fail("\"Off\" button should be shown in Pip menu")
-        uiDevice.findTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.findTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
                 ?: fail("\"Clear\" button should be shown in Pip menu")
         // ... should also contain the "Full screen" and "Close" buttons, ...
         assertFullscreenAndCloseButtonsAreShown()
@@ -231,7 +231,7 @@
         assertNull("\"Pause\" button should not be shown in menu when there are custom actions",
                 uiDevice.findTvPipMenuElementWithDescription(pauseButtonDescription))
 
-        uiDevice.clickTvPipMenuElementWithDescription(Components.PipActivity.MENU_ACTION_CLEAR)
+        uiDevice.clickTvPipMenuElementWithDescription(ActivityOptions.Pip.MENU_ACTION_CLEAR)
         // Invoking the "Clear" action should remove all the custom actions, which should bring up
         // media buttons...
         uiDevice.waitForTvPipMenuElementWithDescription(pauseButtonDescription)
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 3c439fd..7dbd279 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
@@ -28,7 +28,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
 import org.junit.FixMethodOrder
@@ -48,16 +47,16 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
 class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-    private val textEditApp = SplitScreenHelper.getIme(instrumentation)
+    private val textEditApp = SplitScreenUtils.getIme(instrumentation)
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp)
             }
             transitions {
-                SplitScreenHelper.copyContentInSplit(
+                SplitScreenUtils.copyContentInSplit(
                     instrumentation, device, primaryApp, 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 60e5f78..3646fd7 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
@@ -30,7 +30,6 @@
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
@@ -57,14 +56,14 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
                 if (tapl.isTablet) {
-                    SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
                         dragToRight = false, dragToBottom = true)
                 } else {
-                    SplitScreenHelper.dragDividerToDismissSplit(device, wmHelper,
+                    SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
                         dragToRight = true, dragToBottom = true)
                 }
                 wmHelper.StateSyncBuilder()
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 2db3009..80abedd 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
@@ -28,7 +28,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesInvisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
 import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
@@ -56,7 +55,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
                 tapl.goHome()
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 fddd84c..2915787 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
@@ -29,7 +29,6 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowKeepVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
 import org.junit.FixMethodOrder
@@ -54,10 +53,10 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
-                SplitScreenHelper.dragDividerToResizeAndWait(device, wmHelper)
+                SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
             }
         }
 
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 a7c6898..8e041a7 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
@@ -30,7 +30,6 @@
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -76,7 +75,7 @@
                         .openAllApps()
                         .getAppIcon(secondaryApp.appName)
                         .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, 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 7d8a8db..047bcfe 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
@@ -31,7 +31,6 @@
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -60,7 +59,7 @@
     testSpec: FlickerTestParameter
 ) : SplitScreenBase(testSpec) {
 
-    private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+    private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
 
     @Before
     fun before() {
@@ -76,7 +75,7 @@
                 sendNotificationApp.launchViaIntent(wmHelper)
                 val sendNotification = device.wait(
                     Until.findObject(By.text("Send Notification")),
-                    SplitScreenHelper.TIMEOUT_MS
+                    SplitScreenUtils.TIMEOUT_MS
                 )
                 sendNotification?.click() ?: error("Send notification button not found")
 
@@ -84,8 +83,8 @@
                 primaryApp.launchViaIntent(wmHelper)
             }
             transitions {
-                SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
+                SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
             }
             teardown {
                 sendNotificationApp.exit(wmHelper)
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 bfd8a3a..a11874e 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
@@ -30,7 +30,6 @@
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowBecomesVisible
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
@@ -70,7 +69,7 @@
             super.transition(this)
             setup {
                 tapl.goHome()
-                SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(
                     tapl, secondaryApp.appName
                 )
                 primaryApp.launchViaIntent(wmHelper)
@@ -79,7 +78,7 @@
                 tapl.launchedAppState.taskbar
                     .getAppIcon(secondaryApp.appName)
                     .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, 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 cefb9f5..6064b52 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
@@ -26,7 +26,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
@@ -64,8 +63,8 @@
                     .waitForAndVerify()
             }
             transitions {
-                SplitScreenHelper.splitFromOverview(tapl)
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.splitFromOverview(tapl)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index eab473c..e6d6379 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -21,12 +21,11 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.wm.shell.flicker.BaseTest
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 
 abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
     protected val context: Context = instrumentation.context
-    protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
-    protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+    protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
+    protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
 
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
new file mode 100644
index 0000000..96b9faa
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -0,0 +1,341 @@
+/*
+ * 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.wm.shell.flicker.splitscreen
+
+import android.app.Instrumentation
+import android.graphics.Point
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.ViewConfiguration
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.NotificationAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.IComponentMatcher
+import com.android.server.wm.traces.common.IComponentNameMatcher
+import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+
+internal object SplitScreenUtils {
+    internal const val TIMEOUT_MS = 3_000L
+    private const val DRAG_DURATION_MS = 1_000L
+    private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+    private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val GESTURE_STEP_MS = 16L
+    private const val LONG_PRESS_TIME_MS = 100L
+    private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
+
+    private val notificationScrollerSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+    private val notificationContentSelector: BySelector
+        get() = By.text("Notification content")
+    private val dividerBarSelector: BySelector
+        get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+
+    fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Primary.LABEL,
+            ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getSecondary(instrumentation: Instrumentation): StandardAppHelper =
+        SimpleAppHelper(
+            instrumentation,
+            ActivityOptions.SplitScreen.Secondary.LABEL,
+            ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent()
+        )
+
+    fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper =
+        NonResizeableAppHelper(instrumentation)
+
+    fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper =
+        NotificationAppHelper(instrumentation)
+
+    fun getIme(instrumentation: Instrumentation): ImeAppHelper =
+        ImeAppHelper(instrumentation)
+
+    fun waitForSplitComplete(
+        wmHelper: WindowManagerStateHelper,
+        primaryApp: IComponentMatcher,
+        secondaryApp: IComponentMatcher,
+    ) {
+        wmHelper.StateSyncBuilder()
+            .withWindowSurfaceAppeared(primaryApp)
+            .withWindowSurfaceAppeared(secondaryApp)
+            .withSplitDividerVisible()
+            .waitForAndVerify()
+    }
+
+    fun enterSplit(
+        wmHelper: WindowManagerStateHelper,
+        tapl: LauncherInstrumentation,
+        primaryApp: StandardAppHelper,
+        secondaryApp: StandardAppHelper
+    ) {
+        tapl.workspace.switchToOverview().dismissAllTasks()
+        primaryApp.launchViaIntent(wmHelper)
+        secondaryApp.launchViaIntent(wmHelper)
+        tapl.goHome()
+        wmHelper.StateSyncBuilder()
+            .withHomeActivityVisible()
+            .waitForAndVerify()
+        splitFromOverview(tapl)
+        waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+    }
+
+    fun splitFromOverview(tapl: LauncherInstrumentation) {
+        // Note: The initial split position in landscape is different between tablet and phone.
+        // In landscape, tablet will let the first app split to right side, and phone will
+        // split to left side.
+        if (tapl.isTablet) {
+            tapl.workspace.switchToOverview().overviewActions
+                .clickSplit()
+                .currentTask
+                .open()
+        } else {
+            tapl.workspace.switchToOverview().currentTask
+                .tapMenu()
+                .tapSplitMenuItem()
+                .currentTask
+                .open()
+        }
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun dragFromNotificationToSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds = wmHelper.currentState.layerState
+            .displays.firstOrNull { !it.isVirtual }
+            ?.layerStackSpace
+            ?: error("Display not found")
+
+        // Pull down the notifications
+        device.swipe(
+            displayBounds.centerX(), 5,
+            displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+
+        // Find the target notification
+        val notificationScroller = device.wait(
+            Until.findObject(notificationScrollerSelector), TIMEOUT_MS
+        )
+        var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+        while (notificationContent == null) {
+            device.swipe(
+                displayBounds.centerX(), displayBounds.centerY(),
+                displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
+            )
+            notificationContent = notificationScroller.findObject(notificationContentSelector)
+        }
+
+        // Drag to split
+        val dragStart = notificationContent.visibleCenter
+        val dragMiddle = Point(dragStart.x + 50, dragStart.y)
+        val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+        val downTime = SystemClock.uptimeMillis()
+
+        touch(
+            instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
+            TIMEOUT_MS, dragStart
+        )
+        // It needs a horizontal movement to trigger the drag
+        touchMove(
+            instrumentation, downTime, SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS, dragStart, dragMiddle
+        )
+        touchMove(
+            instrumentation, downTime, SystemClock.uptimeMillis(),
+            DRAG_DURATION_MS, dragMiddle, dragEnd
+        )
+        // Wait for a while to start splitting
+        SystemClock.sleep(TIMEOUT_MS)
+        touch(
+            instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
+            GESTURE_STEP_MS, dragEnd
+        )
+        SystemClock.sleep(TIMEOUT_MS)
+    }
+
+    fun touch(
+        instrumentation: Instrumentation,
+        action: Int,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        point: Point
+    ) {
+        val motionEvent = MotionEvent.obtain(
+            downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
+        )
+        motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+        motionEvent.recycle()
+        SystemClock.sleep(duration)
+    }
+
+    fun touchMove(
+        instrumentation: Instrumentation,
+        downTime: Long,
+        eventTime: Long,
+        duration: Long,
+        from: Point,
+        to: Point
+    ) {
+        val steps: Long = duration / GESTURE_STEP_MS
+        var currentTime = eventTime
+        var currentX = from.x.toFloat()
+        var currentY = from.y.toFloat()
+        val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+        val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+        for (i in 1..steps) {
+            val motionMove = MotionEvent.obtain(
+                downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
+            )
+            motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+            instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+            motionMove.recycle()
+
+            currentTime += GESTURE_STEP_MS
+            if (i == steps - 1) {
+                currentX = to.x.toFloat()
+                currentY = to.y.toFloat()
+            } else {
+                currentX += stepX
+                currentY += stepY
+            }
+            SystemClock.sleep(GESTURE_STEP_MS)
+        }
+    }
+
+    fun longPress(
+        instrumentation: Instrumentation,
+        point: Point
+    ) {
+        val downTime = SystemClock.uptimeMillis()
+        touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, point)
+        SystemClock.sleep(LONG_PRESS_TIME_MS)
+        touch(instrumentation, MotionEvent.ACTION_UP, downTime, downTime, TIMEOUT_MS, point)
+    }
+
+    fun createShortcutOnHotseatIfNotExist(
+        tapl: LauncherInstrumentation,
+        appName: String
+    ) {
+        tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0))
+        val allApps = tapl.workspace.switchToAllApps()
+        allApps.freeze()
+        try {
+            allApps.getAppIcon(appName).dragToHotseat(0)
+        } finally {
+            allApps.unfreeze()
+        }
+    }
+
+    fun dragDividerToResizeAndWait(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper
+    ) {
+        val displayBounds = wmHelper.currentState.layerState
+            .displays.firstOrNull { !it.isVirtual }
+            ?.layerStackSpace
+            ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3))
+
+        wmHelper.StateSyncBuilder()
+            .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER)
+            .waitForAndVerify()
+    }
+
+    fun dragDividerToDismissSplit(
+        device: UiDevice,
+        wmHelper: WindowManagerStateHelper,
+        dragToRight: Boolean,
+        dragToBottom: Boolean
+    ) {
+        val displayBounds = wmHelper.currentState.layerState
+            .displays.firstOrNull { !it.isVirtual }
+            ?.layerStackSpace
+            ?: error("Display not found")
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        dividerBar.drag(Point(
+            if (dragToRight) {
+                displayBounds.width * 4 / 5
+            } else {
+                displayBounds.width * 1 / 5
+            },
+            if (dragToBottom) {
+                displayBounds.height * 4 / 5
+            } else {
+                displayBounds.height * 1 / 5
+            }))
+    }
+
+    fun doubleTapDividerToSwitch(device: UiDevice) {
+        val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS)
+        val interval = (ViewConfiguration.getDoubleTapTimeout() +
+            ViewConfiguration.getDoubleTapMinTime()) / 2
+        dividerBar.click()
+        SystemClock.sleep(interval.toLong())
+        dividerBar.click()
+    }
+
+    fun copyContentInSplit(
+        instrumentation: Instrumentation,
+        device: UiDevice,
+        sourceApp: IComponentNameMatcher,
+        destinationApp: IComponentNameMatcher,
+    ) {
+        // Copy text from sourceApp
+        val textView = device.wait(Until.findObject(
+            By.res(sourceApp.packageName, "SplitScreenTest")), TIMEOUT_MS)
+        longPress(instrumentation, textView.visibleCenter)
+
+        val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS)
+        copyBtn.click()
+
+        // Paste text to destinationApp
+        val editText = device.wait(Until.findObject(
+            By.res(destinationApp.packageName, "plain_text_input")), TIMEOUT_MS)
+        longPress(instrumentation, editText.visibleCenter)
+
+        val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS)
+        pasteBtn.click()
+
+        // Verify text
+        if (!textView.text.contentEquals(editText.text)) {
+            error("Fail to copy content in split")
+        }
+    }
+}
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 2d5d2b7..6697b64 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
@@ -29,7 +29,6 @@
 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.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerIsVisibleAtEnd
 import com.android.wm.shell.flicker.layerKeepVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -55,10 +54,10 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
             }
             transitions {
-                SplitScreenHelper.doubleTapDividerToSwitch(device)
+                SplitScreenUtils.doubleTapDividerToSwitch(device)
                 wmHelper.StateSyncBuilder()
                     .withAppTransitionIdle()
                     .waitForAndVerify()
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 20c6af7..5c30116 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
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -48,13 +47,13 @@
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @Group1
 class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
-    val thirdApp = SplitScreenHelper.getNonResizeable(instrumentation)
+    val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
 
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 thirdApp.launchViaIntent(wmHelper)
                 wmHelper.StateSyncBuilder()
@@ -63,7 +62,7 @@
             }
             transitions {
                 tapl.launchedAppState.quickSwitchToPreviousApp()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, 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 cb9ca9f..9c66a37 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
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -53,7 +52,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder()
@@ -62,7 +61,7 @@
             }
             transitions {
                 tapl.workspace.quickSwitchToPreviousApp()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, 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 2662767..e8862bd 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
@@ -27,7 +27,6 @@
 import com.android.server.wm.flicker.annotation.Group1
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.wm.shell.flicker.appWindowBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
 import com.android.wm.shell.flicker.layerBecomesVisible
 import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
@@ -53,7 +52,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenHelper.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder()
@@ -64,7 +63,7 @@
                 tapl.workspace.switchToOverview()
                     .currentTask
                     .open()
-                SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
deleted file mode 100644
index ea606df..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2020 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 {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test {
-    name: "WMShellFlickerTestApp",
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    test_suites: ["device-tests"],
-}
-
-java_library {
-    name: "wmshell-flicker-test-components",
-    srcs: ["src/**/Components.java"],
-    sdk_version: "test_current",
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
deleted file mode 100644
index bc0b0b6..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 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.wm.shell.flicker.testapp">
-
-    <uses-sdk android:minSdkVersion="29"
-         android:targetSdkVersion="29"/>
-    <application android:allowBackup="false"
-         android:supportsRtl="true">
-        <activity android:name=".FixedActivity"
-                  android:resizeableActivity="true"
-                  android:supportsPictureInPicture="true"
-                  android:launchMode="singleTop"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="FixedApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity android:name=".PipActivity"
-                 android:resizeableActivity="true"
-                 android:supportsPictureInPicture="true"
-                 android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
-                 android:taskAffinity="com.android.wm.shell.flicker.testapp.PipActivity"
-                 android:theme="@style/CutoutShortEdges"
-                 android:launchMode="singleTop"
-                 android:label="PipApp"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".ImeActivity"
-                 android:taskAffinity="com.android.wm.shell.flicker.testapp.ImeActivity"
-                 android:theme="@style/CutoutShortEdges"
-                 android:label="ImeApp"
-                 android:launchMode="singleTop"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".SplitScreenActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SplitScreenPrimaryApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".SplitScreenSecondaryActivity"
-                  android:resizeableActivity="true"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SplitScreenSecondaryActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SplitScreenSecondaryApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".SendNotificationActivity"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SendNotificationApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".NonResizeableActivity"
-                  android:resizeableActivity="false"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="NonResizeableApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name=".SimpleActivity"
-                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SimpleActivity"
-                  android:theme="@style/CutoutShortEdges"
-                  android:label="SimpleApp"
-                  android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".LaunchBubbleActivity"
-            android:label="LaunchBubbleApp"
-            android:exported="true"
-            android:theme="@style/CutoutShortEdges"
-            android:launchMode="singleTop">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <activity
-            android:name=".BubbleActivity"
-            android:label="BubbleApp"
-            android:exported="false"
-            android:theme="@style/CutoutShortEdges"
-            android:resizeableActivity="true" />
-    </application>
-</manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
deleted file mode 100644
index b43f31d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_bubble.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
-    <path
-        android:fillColor="#FF000000"
-        android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
deleted file mode 100644
index 0e8c7a0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/ic_message.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
-      android:fillColor="#000000"
-      android:fillType="evenOdd"/>
-</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
deleted file mode 100644
index 4708cfd..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_ime.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:focusableInTouchMode="true"
-    android:background="@android:color/holo_green_light">
-    <EditText android:id="@+id/plain_text_input"
-              android:layout_height="wrap_content"
-              android:layout_width="match_parent"
-              android:inputType="text"/>
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
deleted file mode 100644
index 45d5917..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_non_resizeable.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_orange_light">
-
-    <TextView
-        android:id="@+id/NonResizeableTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:text="NonResizeableActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
deleted file mode 100644
index 8d59b56..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2021 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.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/black">
-
-        <Button
-            android:id="@+id/button_send_notification"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_centerHorizontal="true"
-            android:layout_centerVertical="true"
-            android:text="Send Notification" />
-</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
deleted file mode 100644
index 5d94e51..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_simple.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2018 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@android:color/holo_orange_light">
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
deleted file mode 100644
index 642a08b5..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_green_light">
-
-    <TextView
-        android:id="@+id/SplitScreenTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:textIsSelectable="true"
-        android:text="PrimaryActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
deleted file mode 100644
index 674bb70..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2020 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.
--->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:background="@android:color/holo_blue_light">
-
-    <TextView
-        android:id="@+id/SplitScreenTest"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:gravity="center_vertical|center_horizontal"
-        android:text="SecondaryActivity"
-        android:textAppearance="?android:attr/textAppearanceLarge"/>
-
-</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
deleted file mode 100644
index 23b51cc..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/values/styles.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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.
-  -->
-
-<resources>
-    <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault">
-        <item name="android:windowBackground">@android:color/darker_gray</item>
-    </style>
-
-    <style name="CutoutDefault" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">default</item>
-    </style>
-
-    <style name="CutoutShortEdges" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-
-    <style name="CutoutNever" parent="@style/DefaultTheme">
-        <item name="android:windowLayoutInDisplayCutoutMode">never</item>
-    </style>
-</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
deleted file mode 100644
index a2b580d..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.testapp;
-
-import android.content.ComponentName;
-
-public class Components {
-    public static final String PACKAGE_NAME = "com.android.wm.shell.flicker.testapp";
-
-    public static class SimpleActivity {
-        public static final String LABEL = "SimpleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SimpleActivity");
-    }
-
-    public static class FixedActivity {
-        public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
-        public static final String LABEL = "FixedApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".FixedActivity");
-    }
-
-    public static class NonResizeableActivity {
-        public static final String LABEL = "NonResizeableApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".NonResizeableActivity");
-    }
-
-    public static class PipActivity {
-        // Test App > Pip Activity
-        public static final String LABEL = "PipApp";
-        public static final String MENU_ACTION_NO_OP = "No-Op";
-        public static final String MENU_ACTION_ON = "On";
-        public static final String MENU_ACTION_OFF = "Off";
-        public static final String MENU_ACTION_CLEAR = "Clear";
-
-        // Intent action that this activity dynamically registers to enter picture-in-picture
-        public static final String ACTION_ENTER_PIP = PACKAGE_NAME + ".PipActivity.ENTER_PIP";
-        // Intent action that this activity dynamically registers to set requested orientation.
-        // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
-        public static final String ACTION_SET_REQUESTED_ORIENTATION =
-                PACKAGE_NAME + ".PipActivity.SET_REQUESTED_ORIENTATION";
-
-        // Calls enterPictureInPicture() on creation
-        public static final String EXTRA_ENTER_PIP = "enter_pip";
-        // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
-        public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
-        // Adds a click listener to finish this activity when it is clicked
-        public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
-
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".PipActivity");
-    }
-
-    public static class ImeActivity {
-        public static final String LABEL = "ImeApp";
-        public static final String ACTION_CLOSE_IME =
-                PACKAGE_NAME + ".action.CLOSE_IME";
-        public static final String ACTION_OPEN_IME =
-                PACKAGE_NAME + ".action.OPEN_IME";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".ImeActivity");
-    }
-
-    public static class SplitScreenActivity {
-        public static final String LABEL = "SplitScreenPrimaryApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SplitScreenActivity");
-    }
-
-    public static class SplitScreenSecondaryActivity {
-        public static final String LABEL = "SplitScreenSecondaryApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SplitScreenSecondaryActivity");
-    }
-
-    public static class SendNotificationActivity {
-        public static final String LABEL = "SendNotificationApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".SendNotificationActivity");
-    }
-
-    public static class LaunchBubbleActivity {
-        public static final String LABEL = "LaunchBubbleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".LaunchBubbleActivity");
-    }
-
-    public static class BubbleActivity {
-        public static final String LABEL = "BubbleApp";
-        public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
-                PACKAGE_NAME + ".BubbleActivity");
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
deleted file mode 100644
index 59c64a1..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/ImeActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
-
-public class ImeActivity extends Activity {
-    private static final String ACTION_OPEN_IME =
-            "com.android.wm.shell.flicker.testapp.action.OPEN_IME";
-    private static final String ACTION_CLOSE_IME =
-            "com.android.wm.shell.flicker.testapp.action.CLOSE_IME";
-
-    private InputMethodManager mImm;
-    private View mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        WindowManager.LayoutParams p = getWindow().getAttributes();
-        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
-                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        getWindow().setAttributes(p);
-        setContentView(R.layout.activity_ime);
-
-        mEditText = findViewById(R.id.plain_text_input);
-        mImm = getSystemService(InputMethodManager.class);
-
-        handleIntent(getIntent());
-    }
-
-    @Override
-    protected void onNewIntent(Intent intent) {
-        super.onNewIntent(intent);
-        handleIntent(intent);
-    }
-
-    private void handleIntent(Intent intent) {
-        final String action = intent.getAction();
-        if (ACTION_OPEN_IME.equals(action)) {
-            mEditText.requestFocus();
-            mImm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED);
-        } else if (ACTION_CLOSE_IME.equals(action)) {
-            mImm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
-            mEditText.clearFocus();
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
deleted file mode 100644
index 24275e0..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/NonResizeableActivity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2020 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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class NonResizeableActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-        setContentView(R.layout.activity_non_resizeable);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
deleted file mode 100644
index 5343c18..0000000
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SimpleActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2018 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.wm.shell.flicker.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class SimpleActivity extends Activity {
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        WindowManager.LayoutParams p = getWindow().getAttributes();
-        p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
-                .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
-        getWindow().setAttributes(p);
-        setContentView(R.layout.activity_simple);
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index a7234c1..98b5912 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -18,7 +18,9 @@
 
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -27,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.animation.Animator;
 import android.window.TransitionInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -76,4 +79,18 @@
 
         verify(mController).onAnimationFinished(mTransition);
     }
+
+    @Test
+    public void testChangesBehindStartingWindow() {
+        final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0);
+        final TransitionInfo.Change embeddingChange = createChange();
+        embeddingChange.setFlags(FLAG_IS_BEHIND_STARTING_WINDOW);
+        info.addChange(embeddingChange);
+        final Animator animator = mAnimRunner.createAnimator(
+                info, mStartTransaction, mFinishTransaction,
+                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+
+        // The animation should be empty when it is behind starting window.
+        assertEquals(0, animator.getDuration());
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 5779425..c628f399 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -32,13 +32,14 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.testing.AndroidTestingRunner;
+import android.window.DisplayAreaInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransaction.Change;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.ShellExecutor;
@@ -59,7 +60,7 @@
     @Mock
     private ShellTaskOrganizer mShellTaskOrganizer;
     @Mock
-    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+    private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     @Mock
     private ShellExecutor mTestExecutor;
     @Mock
@@ -75,7 +76,7 @@
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
 
         mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
-                mRootDisplayAreaOrganizer, mMockHandler, mMockTransitions);
+                mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
 
         mShellInit.init();
     }
@@ -94,19 +95,19 @@
         when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
                 mContext.getDisplayId())).thenReturn(taskWct);
 
-        // Create a fake WCT to simulate setting display windowing mode to freeform
-        WindowContainerTransaction displayWct = new WindowContainerTransaction();
+        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
         MockToken displayMockToken = new MockToken();
-        displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
-        when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
-                WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
+                mContext.getDisplayId(), 0);
+        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+                .thenReturn(displayAreaInfo);
 
         // The test
         mController.updateDesktopModeActive(true);
 
         ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
                 WindowContainerTransaction.class);
-        verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
 
         // WCT should have 2 changes - clear task wm mode and set display wm mode
         WindowContainerTransaction wct = arg.getValue();
@@ -118,7 +119,7 @@
         assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
 
         // Verify executed WCT has a change for setting display windowing mode to freeform
-        Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
         assertThat(displayWmModeChange).isNotNull();
         assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
     }
@@ -139,19 +140,19 @@
         when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
                 mContext.getDisplayId())).thenReturn(taskBoundsWct);
 
-        // Create a fake WCT to simulate setting display windowing mode to fullscreen
-        WindowContainerTransaction displayWct = new WindowContainerTransaction();
+        // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
         MockToken displayMockToken = new MockToken();
-        displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
-        when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
-                WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+        DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
+                mContext.getDisplayId(), 0);
+        when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+                .thenReturn(displayAreaInfo);
 
         // The test
         mController.updateDesktopModeActive(false);
 
         ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
                 WindowContainerTransaction.class);
-        verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+        verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
 
         // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
         WindowContainerTransaction wct = arg.getValue();
@@ -171,7 +172,7 @@
                 .isTrue();
 
         // Verify executed WCT has a change for setting display windowing mode to fullscreen
-        Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+        Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
         assertThat(displayWmModeChange).isNotNull();
         assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 9240abf..8350870 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -16,7 +16,10 @@
 
 package com.android.wm.shell.splitscreen;
 
+import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -28,11 +31,10 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -42,8 +44,10 @@
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -115,7 +119,6 @@
                 mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
                 mMainExecutor, Optional.empty()));
-        doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
 
         when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
         when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
@@ -303,4 +306,16 @@
 
         verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false));
     }
+
+    @Test
+    public void testAddActivityOptions_addsBackgroundActivitiesFlags() {
+        Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN,
+                SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */);
+
+        assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class),
+                mMainStage.mRootTaskInfo.token);
+        assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED));
+        assertTrue(options.getBoolean(
+                KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION));
+    }
 }
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 1f96617..cb0f22f 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -238,6 +238,7 @@
     ASurfaceControl_createFromWindow; # introduced=29
     ASurfaceControl_acquire; # introduced=31
     ASurfaceControl_release; # introduced=29
+    ASurfaceControl_fromSurfaceControl; # introduced=34
     ASurfaceTexture_acquireANativeWindow; # introduced=28
     ASurfaceTexture_attachToGLContext; # introduced=28
     ASurfaceTexture_detachFromGLContext; # introduced=28
@@ -255,6 +256,7 @@
     ASurfaceTransaction_apply; # introduced=29
     ASurfaceTransaction_create; # introduced=29
     ASurfaceTransaction_delete; # introduced=29
+    ASurfaceTransaction_fromTransaction; # introduced=34
     ASurfaceTransaction_reparent; # introduced=29
     ASurfaceTransaction_setBuffer; # introduced=29
     ASurfaceTransaction_setBufferAlpha; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 42f4406..9e4d726 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -17,6 +17,8 @@
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/native_window.h>
 #include <android/surface_control.h>
+#include <android/surface_control_jni.h>
+#include <android_runtime/android_view_SurfaceControl.h>
 #include <configstore/Utils.h>
 #include <gui/HdrMetadata.h>
 #include <gui/ISurfaceComposer.h>
@@ -28,6 +30,8 @@
 #include <ui/DynamicDisplayInfo.h>
 #include <utils/Timers.h>
 
+#include <utility>
+
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
 using namespace android;
@@ -134,6 +138,11 @@
     SurfaceControl_release(surfaceControl);
 }
 
+ASurfaceControl* ASurfaceControl_fromSurfaceControl(JNIEnv* env, jobject surfaceControlObj) {
+    return reinterpret_cast<ASurfaceControl*>(
+            android_view_SurfaceControl_getNativeSurfaceControl(env, surfaceControlObj));
+}
+
 struct ASurfaceControlStats {
     std::variant<int64_t, sp<Fence>> acquireTimeOrFence;
     sp<Fence> previousReleaseFence;
@@ -190,6 +199,11 @@
     delete transaction;
 }
 
+ASurfaceTransaction* ASurfaceTransaction_fromTransaction(JNIEnv* env, jobject transactionObj) {
+    return reinterpret_cast<ASurfaceTransaction*>(
+            android_view_SurfaceTransaction_getNativeSurfaceTransaction(env, transactionObj));
+}
+
 void ASurfaceTransaction_apply(ASurfaceTransaction* aSurfaceTransaction) {
     CHECK_NOT_NULL(aSurfaceTransaction);
 
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
new file mode 100644
index 0000000..ed92af9
--- /dev/null
+++ b/packages/CredentialManager/Android.bp
@@ -0,0 +1,36 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "CredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+    srcs: ["src/**/*.kt"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.activity_activity-compose",
+        "androidx.appcompat_appcompat",
+        "androidx.compose.material_material",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui",
+        "androidx.compose.ui_ui-tooling",
+        "androidx.core_core-ktx",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "androidx.navigation_navigation-compose",
+        "androidx.recyclerview_recyclerview",
+    ],
+
+    platform_apis: true,
+
+    kotlincflags: ["-Xjvm-default=enable"],
+}
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
new file mode 100644
index 0000000..586ef86
--- /dev/null
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 Google Inc.
+ *
+ * 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.credentialmanager">
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+
+    <application
+      android:allowBackup="true"
+      android:dataExtractionRules="@xml/data_extraction_rules"
+      android:fullBackupContent="@xml/backup_rules"
+      android:icon="@mipmap/ic_launcher"
+      android:label="@string/app_name"
+      android:roundIcon="@mipmap/ic_launcher_round"
+      android:supportsRtl="true"
+      android:theme="@style/Theme.CredentialSelector">
+
+    <activity
+        android:name=".CredentialSelectorActivity"
+        android:exported="true"
+        android:label="@string/app_name"
+        android:launchMode="singleInstance"
+        android:noHistory="true"
+        android:excludeFromRecents="true"
+        android:theme="@style/Theme.CredentialSelector">
+    </activity>
+  </application>
+
+</manifest>
diff --git a/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..966abaf
--- /dev/null
+++ b/packages/CredentialManager/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+  <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+    <aapt:attr name="android:fillColor">
+      <gradient
+          android:endX="85.84757"
+          android:endY="92.4963"
+          android:startX="42.9492"
+          android:startY="49.59793"
+          android:type="linear">
+        <item
+            android:color="#44000000"
+            android:offset="0.0" />
+        <item
+            android:color="#00000000"
+            android:offset="1.0" />
+      </gradient>
+    </aapt:attr>
+  </path>
+  <path
+      android:fillColor="#FFFFFF"
+      android:fillType="nonZero"
+      android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+      android:strokeColor="#00000000"
+      android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_launcher_background.xml b/packages/CredentialManager/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..61bb79e
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportHeight="108"
+    android:viewportWidth="108">
+  <path
+      android:fillColor="#3DDC84"
+      android:pathData="M0,0h108v108h-108z" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M9,0L9,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,0L19,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M29,0L29,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M39,0L39,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M49,0L49,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M59,0L59,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M69,0L69,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M79,0L79,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M89,0L89,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M99,0L99,108"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,9L108,9"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,19L108,19"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,29L108,29"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,39L108,39"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,49L108,49"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,59L108,59"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,69L108,69"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,79L108,79"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,89L108,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M0,99L108,99"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,29L89,29"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,39L89,39"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,49L89,49"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,59L89,59"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,69L89,69"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M19,79L89,79"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M29,19L29,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M39,19L39,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M49,19L49,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M59,19L59,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M69,19L69,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+  <path
+      android:fillColor="#00000000"
+      android:pathData="M79,19L79,89"
+      android:strokeColor="#33FFFFFF"
+      android:strokeWidth="0.8" />
+</vector>
diff --git a/packages/CredentialManager/res/drawable/ic_passkey.xml b/packages/CredentialManager/res/drawable/ic_passkey.xml
new file mode 100644
index 0000000..041a321
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkey.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="24dp"
+    android:viewportWidth="28"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M27.453,13.253C27.453,14.952 26.424,16.411 24.955,17.041L26.21,18.295L24.839,19.666L26.21,21.037L23.305,23.942L22.012,22.65L22.012,17.156C20.385,16.605 19.213,15.066 19.213,13.253C19.213,10.977 21.058,9.133 23.333,9.133C25.609,9.133 27.453,10.977 27.453,13.253ZM25.47,13.254C25.47,14.434 24.514,15.39 23.334,15.39C22.154,15.39 21.197,14.434 21.197,13.254C21.197,12.074 22.154,11.118 23.334,11.118C24.514,11.118 25.47,12.074 25.47,13.254Z"
+      android:fillColor="#00639B"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M17.85,5.768C17.85,8.953 15.268,11.536 12.083,11.536C8.897,11.536 6.315,8.953 6.315,5.768C6.315,2.582 8.897,0 12.083,0C15.268,0 17.85,2.582 17.85,5.768Z"
+      android:fillColor="#00639B"/>
+  <path
+      android:pathData="M0.547,20.15C0.547,16.32 8.23,14.382 12.083,14.382C13.59,14.382 15.684,14.679 17.674,15.269C18.116,16.454 18.952,17.447 20.022,18.089V23.071H0.547V20.15Z"
+      android:fillColor="#00639B"/>
+</vector>
diff --git a/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..03eed25
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <background android:drawable="@drawable/ic_launcher_background" />
+  <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..03eed25
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <background android:drawable="@drawable/ic_launcher_background" />
+  <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f508
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/packages/CredentialManager/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
new file mode 100644
index 0000000..09837df62
--- /dev/null
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="purple_200">#FFBB86FC</color>
+  <color name="purple_500">#FF6200EE</color>
+  <color name="purple_700">#FF3700B3</color>
+  <color name="teal_200">#FF03DAC5</color>
+  <color name="teal_700">#FF018786</color>
+  <color name="black">#FF000000</color>
+  <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
new file mode 100644
index 0000000..2901705
--- /dev/null
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -0,0 +1,13 @@
+<resources>
+  <string name="app_name">CredentialManager</string>
+  <string name="string_cancel">Cancel</string>
+  <string name="string_continue">Continue</string>
+  <string name="string_more_options">More options</string>
+  <string name="string_no_thanks">No thanks</string>
+  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
+  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+  <string name="choose_provider_title">Choose your default provider</string>
+  <string name="choose_provider_body">This provider will store passkeys and passwords for you and help you easily autofill and sign in. Learn more</string>
+  <string name="choose_create_option_title">Create a passkey at</string>
+  <string name="choose_sign_in_title">Use saved sign in</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
new file mode 100644
index 0000000..feec746
--- /dev/null
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+  <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
+    <item name="android:statusBarColor">@color/purple_700</item>
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:windowNoTitle">true</item>
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowIsTranslucent">true</item>
+    <item name="android:colorBackgroundCacheHint">@null</item>
+  </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/backup_rules.xml b/packages/CredentialManager/res/xml/backup_rules.xml
new file mode 100644
index 0000000..9b42d90
--- /dev/null
+++ b/packages/CredentialManager/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+  <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/xml/data_extraction_rules.xml b/packages/CredentialManager/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6c3bb0
--- /dev/null
+++ b/packages/CredentialManager/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+  <cloud-backup>
+    <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+  </cloud-backup>
+  <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
new file mode 100644
index 0000000..f20104a
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -0,0 +1,131 @@
+package com.android.credentialmanager
+
+import android.content.Context
+import com.android.credentialmanager.createflow.CreateOptionInfo
+import com.android.credentialmanager.createflow.ProviderInfo
+import com.android.credentialmanager.createflow.ProviderList
+import com.android.credentialmanager.getflow.CredentialOptionInfo
+
+class CredentialManagerRepo(
+  private val context: Context
+) {
+  fun getCredentialProviderList(): List<com.android.credentialmanager.getflow.ProviderInfo> {
+    return listOf(
+      com.android.credentialmanager.getflow.ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Google Password Manager",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        credentialOptions = listOf(
+          CredentialOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@gmail.com",
+            id = "id-1",
+          ),
+          CredentialOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett Work",
+            subtitle = "elisa.beckett.work@google.com",
+            id = "id-2",
+          ),
+        )
+      ),
+      com.android.credentialmanager.getflow.ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Lastpass",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        credentialOptions = listOf(
+          CredentialOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@lastpass.com",
+            id = "id-1",
+          ),
+        )
+      ),
+      com.android.credentialmanager.getflow.ProviderInfo(
+        icon = context.getDrawable(R.drawable.ic_passkey)!!,
+        name = "Dashlane",
+        appDomainName = "tribank.us",
+        credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+        credentialOptions = listOf(
+          CredentialOptionInfo(
+            icon = context.getDrawable(R.drawable.ic_passkey)!!,
+            title = "Elisa Backett",
+            subtitle = "elisa.beckett@dashlane.com",
+            id = "id-1",
+          ),
+        )
+      ),
+    )
+  }
+
+  fun createCredentialProviderList(): ProviderList {
+    return ProviderList(
+      listOf(
+        ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Google Password Manager",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          createOptions = listOf(
+            CreateOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@gmail.com",
+              id = "id-1",
+            ),
+            CreateOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett Work",
+              subtitle = "elisa.beckett.work@google.com",
+              id = "id-2",
+            ),
+          )
+        ),
+        ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Lastpass",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          createOptions = listOf(
+            CreateOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@lastpass.com",
+              id = "id-1",
+            ),
+          )
+        ),
+        ProviderInfo(
+          icon = context.getDrawable(R.drawable.ic_passkey)!!,
+          name = "Dashlane",
+          appDomainName = "tribank.us",
+          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
+          createOptions = listOf(
+            CreateOptionInfo(
+              icon = context.getDrawable(R.drawable.ic_passkey)!!,
+              title = "Elisa Backett",
+              subtitle = "elisa.beckett@dashlane.com",
+              id = "id-1",
+            ),
+          )
+        ),
+      )
+    )
+  }
+
+  companion object {
+    lateinit var repo: CredentialManagerRepo
+
+    fun setup(context: Context) {
+      repo = CredentialManagerRepo(context)
+    }
+
+    fun getInstance(): CredentialManagerRepo {
+      return repo
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..dd4ba11
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,63 @@
+package com.android.credentialmanager
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import com.android.credentialmanager.createflow.CreatePasskeyViewModel
+import com.android.credentialmanager.createflow.createPasskeyGraph
+import com.android.credentialmanager.getflow.GetCredentialViewModel
+import com.android.credentialmanager.getflow.getCredentialsGraph
+import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
+
+@ExperimentalMaterialApi
+class CredentialSelectorActivity : ComponentActivity() {
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    CredentialManagerRepo.setup(this)
+    val startDestination = intent.extras?.getString(
+      "start_destination",
+      "getCredentials"
+    ) ?: "getCredentials"
+
+    setContent {
+      CredentialSelectorTheme {
+        AppNavHost(
+          startDestination = startDestination,
+          onCancel = {this.finish()}
+        )
+      }
+    }
+  }
+
+  @ExperimentalMaterialApi
+  @Composable
+  fun AppNavHost(
+    modifier: Modifier = Modifier,
+    navController: NavHostController = rememberNavController(),
+    startDestination: String,
+    onCancel: () -> Unit,
+  ) {
+    NavHost(
+      modifier = modifier,
+      navController = navController,
+      startDestination = startDestination
+    ) {
+      createPasskeyGraph(
+        navController = navController,
+        viewModel = CreatePasskeyViewModel(CredentialManagerRepo.repo),
+        onCancel = onCancel
+      )
+      getCredentialsGraph(
+        navController = navController,
+        viewModel = GetCredentialViewModel(CredentialManagerRepo.repo),
+        onCancel = onCancel
+      )
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
new file mode 100644
index 0000000..62c244c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -0,0 +1,22 @@
+package com.android.credentialmanager.createflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+  val icon: Drawable,
+  val name: String,
+  val appDomainName: String,
+  val credentialTypeIcon: Drawable,
+  val createOptions: List<CreateOptionInfo>,
+)
+
+data class ProviderList(
+  val providers: List<ProviderInfo>,
+)
+
+data class CreateOptionInfo(
+  val icon: Drawable,
+  val title: String,
+  val subtitle: String,
+  val id: String,
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
new file mode 100644
index 0000000..fb6db21
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -0,0 +1,541 @@
+package com.android.credentialmanager.createflow
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonColors
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavType
+import androidx.navigation.compose.composable
+import androidx.navigation.navArgument
+import androidx.navigation.navigation
+import com.android.credentialmanager.R
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+import com.android.credentialmanager.ui.theme.lightColorAccentSecondary
+import com.android.credentialmanager.ui.theme.lightSurface1
+
+@ExperimentalMaterialApi
+fun NavGraphBuilder.createPasskeyGraph(
+  navController: NavController,
+  viewModel: CreatePasskeyViewModel,
+  onCancel: () -> Unit,
+  startDestination: String = "intro", // TODO: get this from view model
+) {
+  navigation(startDestination = startDestination, route = "createPasskey") {
+    composable("intro") {
+      CreatePasskeyIntroDialog(
+        onCancel = onCancel,
+        onConfirm = {viewModel.onConfirm(navController)}
+      )
+    }
+    composable("providerSelection") {
+        ProviderSelectionDialog(
+          providerList = viewModel.uiState.collectAsState().value.providerList,
+          onProviderSelected = {viewModel.onProviderSelected(it, navController)},
+          onCancel = onCancel
+        )
+    }
+    composable(
+      "createCredentialSelection/{providerName}",
+      arguments = listOf(navArgument("providerName") {type = NavType.StringType})
+    ) {
+      val arguments = it.arguments
+      if (arguments == null) {
+        throw java.lang.IllegalStateException("createCredentialSelection without a provider name")
+      }
+      CreationSelectionDialog(
+        providerInfo = viewModel.getProviderInfoByName(arguments.getString("providerName")!!),
+        onOptionSelected = {viewModel.onCreateOptionSelected(it)},
+        onCancel = onCancel,
+        multiProvider = viewModel.uiState.collectAsState().value.providerList.providers.size > 1,
+        onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)},
+      )
+    }
+  }
+}
+
+/**
+ * BEGIN INTRO CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CreatePasskeyIntroDialog(
+  onCancel: () -> Unit = {},
+  onConfirm: () -> Unit = {},
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      ConfirmationCard(
+        onCancel = onCancel, onConfirm = onConfirm
+      )
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    when (state.currentValue) {
+      ModalBottomSheetValue.Hidden -> {
+        onCancel()
+      }
+    }
+  }
+}
+
+@Composable
+fun ConfirmationCard(
+  onConfirm: () -> Unit,
+  onCancel: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        painter = painterResource(R.drawable.ic_passkey),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = stringResource(R.string.passkey_creation_intro_title),
+        style = Typography.subtitle1,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Text(
+        text = stringResource(R.string.passkey_creation_intro_body),
+        style = Typography.body1,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 48.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.SpaceBetween,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(
+          stringResource(R.string.string_cancel),
+          onclick = onCancel
+        )
+        ConfirmButton(
+          stringResource(R.string.string_continue),
+          onclick = onConfirm
+        )
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+/**
+ * END INTRO CONTENT
+ */
+
+/**
+ * BEGIN PROVIDER SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun ProviderSelectionDialog(
+  providerList: ProviderList,
+  onProviderSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      ProviderSelectionCard(
+        providerList = providerList,
+        onCancel = onCancel,
+        onProviderSelected = onProviderSelected
+      )
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    when (state.currentValue) {
+      ModalBottomSheetValue.Hidden -> {
+        onCancel()
+      }
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderSelectionCard(
+  providerList: ProviderList,
+  onProviderSelected: (String) -> Unit,
+  onCancel: () -> Unit
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Text(
+        text = stringResource(R.string.choose_provider_title),
+        style = Typography.subtitle1,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = stringResource(R.string.choose_provider_body),
+        style = Typography.body1,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerList.providers.forEach {
+            item {
+              ProviderRow(providerInfo = it, onProviderSelected = onProviderSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_cancel), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onProviderSelected(providerInfo.name)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+            bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+            // painter = painterResource(R.drawable.ic_passkey),
+            // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Text(
+      text = providerInfo.name,
+      style = Typography.button,
+      modifier = Modifier.padding(vertical = 18.dp)
+    )
+  }
+}
+
+/**
+ * END PROVIDER SELECTION CONTENT
+ */
+
+/**
+ * BEGIN COMMON COMPONENTS
+ */
+
+@Composable
+fun CancelButton(text: String, onclick: () -> Unit) {
+  val colors = ButtonDefaults.buttonColors(
+    backgroundColor = lightBackgroundColor
+  )
+  NavigationButton(
+    border = BorderStroke(1.dp, lightSurface1),
+    colors = colors,
+    text = text,
+    onclick = onclick)
+}
+
+@Composable
+fun ConfirmButton(text: String, onclick: () -> Unit) {
+  val colors = ButtonDefaults.buttonColors(
+    backgroundColor = lightColorAccentSecondary
+  )
+  NavigationButton(
+    colors = colors,
+    text = text,
+    onclick = onclick)
+}
+
+@Composable
+fun NavigationButton(
+    border: BorderStroke? = null,
+    colors: ButtonColors,
+    text: String,
+    onclick: () -> Unit
+) {
+  Button(
+    onClick = onclick,
+    shape = Shapes.small,
+    colors = colors,
+    border = border
+  ) {
+    Text(text = text, style = Typography.button)
+  }
+}
+
+/**
+ * BEGIN CREATE OPTION SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CreationSelectionDialog(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      CreationSelectionCard(
+        providerInfo = providerInfo,
+        onCancel = onCancel,
+        onOptionSelected = onOptionSelected,
+        multiProvider = multiProvider,
+        onMoreOptionSelected = onMoreOptionSelected,
+      )
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    when (state.currentValue) {
+      ModalBottomSheetValue.Hidden -> {
+        onCancel()
+      }
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreationSelectionCard(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = "${stringResource(R.string.choose_create_option_title)} ${providerInfo.name}",
+        style = Typography.subtitle1,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = providerInfo.appDomainName,
+        style = Typography.body2,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerInfo.createOptions.forEach {
+            item {
+              CreateOptionRow(createOptionInfo = it, onOptionSelected = onOptionSelected)
+            }
+          }
+          if (multiProvider) {
+            item {
+              MoreOptionRow(onSelect = onMoreOptionSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_cancel), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CreateOptionRow(createOptionInfo: CreateOptionInfo, onOptionSelected: (String) -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onOptionSelected(createOptionInfo.id)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+            // painter = painterResource(R.drawable.ic_passkey),
+        // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Column() {
+      Text(
+        text = createOptionInfo.title,
+        style = Typography.h6,
+        modifier = Modifier.padding(top = 16.dp)
+      )
+      Text(
+        text = createOptionInfo.subtitle,
+        style = Typography.body2,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth().height(52.dp),
+    onClick = onSelect,
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+      Text(
+        text = stringResource(R.string.string_more_options),
+        style = Typography.h6,
+      )
+  }
+}
+/**
+ * END CREATE OPTION SELECTION CONTENT
+ */
+
+/**
+ * END COMMON COMPONENTS
+ */
+
+@ExperimentalMaterialApi
+@Preview(showBackground = true)
+@Composable
+fun CreatePasskeyEntryScreenPreview() {
+  // val providers = ProviderList(
+  //   listOf(
+  //     ProviderInfo(null),
+  //     ProviderInfo(null, "Dashlane"),
+  //     ProviderInfo(null, "LastPass")
+  //   )
+  // )
+  // TatiAccountSelectorTheme {
+  //   ConfirmationCard({}, {})
+  // }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
new file mode 100644
index 0000000..3554285
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -0,0 +1,51 @@
+package com.android.credentialmanager.createflow
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.navigation.NavController
+import com.android.credentialmanager.CredentialManagerRepo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+data class CreatePasskeyUiState(
+  val providerList: ProviderList,
+)
+
+class CreatePasskeyViewModel(
+  credManRepo: CredentialManagerRepo
+) : ViewModel() {
+
+  private val _uiState = MutableStateFlow(
+    CreatePasskeyUiState(credManRepo.createCredentialProviderList())
+  )
+  val uiState: StateFlow<CreatePasskeyUiState> = _uiState.asStateFlow()
+
+  fun onConfirm(navController: NavController) {
+    if (uiState.value.providerList.providers.size > 1) {
+      navController.navigate("providerSelection")
+    } else if (uiState.value.providerList.providers.size == 1) {
+      onProviderSelected(uiState.value.providerList.providers[0].name, navController)
+    } else {
+      throw java.lang.IllegalStateException("Empty provider list.")
+    }
+  }
+
+  fun onProviderSelected(providerName: String, navController: NavController) {
+    return navController.navigate("createCredentialSelection/$providerName")
+  }
+
+  fun onCreateOptionSelected(createOptionId: String) {
+    Log.d("Account Selector", "Option selected for creation: $createOptionId")
+  }
+
+  fun getProviderInfoByName(providerName: String): ProviderInfo {
+    return uiState.value.providerList.providers.single {
+      it.name.equals(providerName)
+    }
+  }
+
+  fun onMoreOptionSelected(navController: NavController) {
+    navController.navigate("moreOption")
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
new file mode 100644
index 0000000..6ad14db
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -0,0 +1,232 @@
+package com.android.credentialmanager.getflow
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.Card
+import androidx.compose.material.Chip
+import androidx.compose.material.ChipDefaults
+import androidx.compose.material.Divider
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Icon
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.Text
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import androidx.navigation.NavController
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.compose.composable
+import androidx.navigation.navigation
+import com.android.credentialmanager.R
+import com.android.credentialmanager.createflow.CancelButton
+import com.android.credentialmanager.ui.theme.Grey100
+import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.Typography
+import com.android.credentialmanager.ui.theme.lightBackgroundColor
+
+@ExperimentalMaterialApi
+fun NavGraphBuilder.getCredentialsGraph(
+  navController: NavController,
+  viewModel: GetCredentialViewModel,
+  onCancel: () -> Unit,
+  startDestination: String = "credentialSelection", // TODO: get this from view model
+) {
+  navigation(startDestination = startDestination, route = "getCredentials") {
+    composable("credentialSelection") {
+      CredentialSelectionDialog(
+        providerInfo = viewModel.getDefaultProviderInfo(),
+        onOptionSelected = {viewModel.onCredentailSelected(it, navController)},
+        onCancel = onCancel,
+        multiProvider = viewModel.uiState.collectAsState().value.providers.size > 1,
+        onMoreOptionSelected = {viewModel.onMoreOptionSelected(navController)}
+      )
+    }
+  }
+}
+
+/**
+ * BEGIN CREATE OPTION SELECTION CONTENT
+ */
+@ExperimentalMaterialApi
+@Composable
+fun CredentialSelectionDialog(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  val state = rememberModalBottomSheetState(
+    initialValue = ModalBottomSheetValue.Expanded,
+    skipHalfExpanded = true
+  )
+  ModalBottomSheetLayout(
+    sheetState = state,
+    sheetContent = {
+      CredentialSelectionCard(
+        providerInfo = providerInfo,
+        onCancel = onCancel,
+        onOptionSelected = onOptionSelected,
+        multiProvider = multiProvider,
+        onMoreOptionSelected = onMoreOptionSelected,
+      )
+    },
+    scrimColor = Color.Transparent,
+    sheetShape = Shapes.medium,
+  ) {}
+  LaunchedEffect(state.currentValue) {
+    when (state.currentValue) {
+      ModalBottomSheetValue.Hidden -> {
+        onCancel()
+      }
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialSelectionCard(
+  providerInfo: ProviderInfo,
+  onOptionSelected: (String) -> Unit,
+  onCancel: () -> Unit,
+  multiProvider: Boolean,
+  onMoreOptionSelected: () -> Unit,
+) {
+  Card(
+    backgroundColor = lightBackgroundColor,
+  ) {
+    Column() {
+      Icon(
+        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = null,
+        tint = Color.Unspecified,
+        modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(top = 24.dp)
+      )
+      Text(
+        text = stringResource(R.string.choose_sign_in_title),
+        style = Typography.subtitle1,
+        modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
+      )
+      Text(
+        text = providerInfo.appDomainName,
+        style = Typography.body2,
+        modifier = Modifier.padding(horizontal = 28.dp)
+      )
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Card(
+        shape = Shapes.medium,
+        modifier = Modifier
+          .padding(horizontal = 24.dp)
+          .align(alignment = Alignment.CenterHorizontally)
+      ) {
+        LazyColumn(
+          verticalArrangement = Arrangement.spacedBy(2.dp)
+        ) {
+          providerInfo.credentialOptions.forEach {
+            item {
+              CredentialOptionRow(credentialOptionInfo = it, onOptionSelected = onOptionSelected)
+            }
+          }
+          if (multiProvider) {
+            item {
+              MoreOptionRow(onSelect = onMoreOptionSelected)
+            }
+          }
+        }
+      }
+      Divider(
+        thickness = 24.dp,
+        color = Color.Transparent
+      )
+      Row(
+        horizontalArrangement = Arrangement.Start,
+        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+      ) {
+        CancelButton(stringResource(R.string.string_no_thanks), onCancel)
+      }
+      Divider(
+        thickness = 18.dp,
+        color = Color.Transparent,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun CredentialOptionRow(
+    credentialOptionInfo: CredentialOptionInfo,
+    onOptionSelected: (String) -> Unit
+) {
+  Chip(
+    modifier = Modifier.fillMaxWidth(),
+    onClick = {onOptionSelected(credentialOptionInfo.id)},
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+            bitmap = credentialOptionInfo.icon.toBitmap().asImageBitmap(),
+        // painter = painterResource(R.drawable.ic_passkey),
+        // TODO: add description.
+            contentDescription = "")
+    },
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Column() {
+      Text(
+        text = credentialOptionInfo.title,
+        style = Typography.h6,
+        modifier = Modifier.padding(top = 16.dp)
+      )
+      Text(
+        text = credentialOptionInfo.subtitle,
+        style = Typography.body2,
+        modifier = Modifier.padding(bottom = 16.dp)
+      )
+    }
+  }
+}
+
+@ExperimentalMaterialApi
+@Composable
+fun MoreOptionRow(onSelect: () -> Unit) {
+  Chip(
+    modifier = Modifier.fillMaxWidth().height(52.dp),
+    onClick = onSelect,
+    colors = ChipDefaults.chipColors(
+      backgroundColor = Grey100,
+      leadingIconContentColor = Grey100
+    ),
+    shape = Shapes.large
+  ) {
+    Text(
+      text = stringResource(R.string.string_more_options),
+      style = Typography.h6,
+    )
+  }
+}
+/**
+ * END CREATE OPTION SELECTION CONTENT
+ */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
new file mode 100644
index 0000000..20057de
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -0,0 +1,36 @@
+package com.android.credentialmanager.getflow
+
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.navigation.NavController
+import com.android.credentialmanager.CredentialManagerRepo
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+data class GetCredentialUiState(
+  val providers: List<ProviderInfo>
+)
+
+class GetCredentialViewModel(
+  credManRepo: CredentialManagerRepo
+) : ViewModel() {
+
+  private val _uiState = MutableStateFlow(
+    GetCredentialUiState(credManRepo.getCredentialProviderList())
+  )
+  val uiState: StateFlow<GetCredentialUiState> = _uiState.asStateFlow()
+
+  fun getDefaultProviderInfo(): ProviderInfo {
+    // TODO: correctly get the default provider.
+    return uiState.value.providers.first()
+  }
+
+  fun onCredentailSelected(credentialId: String, navController: NavController) {
+    Log.d("Account Selector", "credential selected: $credentialId")
+  }
+
+  fun onMoreOptionSelected(navController: NavController) {
+    Log.d("Account Selector", "More Option selected")
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
new file mode 100644
index 0000000..8710ece
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -0,0 +1,18 @@
+package com.android.credentialmanager.getflow
+
+import android.graphics.drawable.Drawable
+
+data class ProviderInfo(
+  val icon: Drawable,
+  val name: String,
+  val appDomainName: String,
+  val credentialTypeIcon: Drawable,
+  val credentialOptions: List<CredentialOptionInfo>,
+)
+
+data class CredentialOptionInfo(
+  val icon: Drawable,
+  val title: String,
+  val subtitle: String,
+  val id: String,
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
new file mode 100644
index 0000000..abb4bfb
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Color.kt
@@ -0,0 +1,14 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Grey100 = Color(0xFFF1F3F4)
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
+val lightColorAccentSecondary = Color(0xFFC2E7FF)
+val lightBackgroundColor = Color(0xFFF0F0F0)
+val lightSurface1 = Color(0xFF6991D6)
+val textColorSecondary = Color(0xFF40484B)
+val textColorPrimary = Color(0xFF191C1D)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
new file mode 100644
index 0000000..cba8658
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+  small = RoundedCornerShape(100.dp),
+  medium = RoundedCornerShape(20.dp),
+  large = RoundedCornerShape(0.dp)
+)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
new file mode 100644
index 0000000..a9d20ae
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -0,0 +1,47 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+  primary = Purple200,
+  primaryVariant = Purple700,
+  secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+  primary = Purple500,
+  primaryVariant = Purple700,
+  secondary = Teal200
+
+  /* Other default colors to override
+    background = Color.White,
+    surface = Color.White,
+    onPrimary = Color.White,
+    onSecondary = Color.Black,
+    onBackground = Color.Black,
+    onSurface = Color.Black,
+    */
+)
+
+@Composable
+fun CredentialSelectorTheme(
+  darkTheme: Boolean = isSystemInDarkTheme(),
+  content: @Composable () -> Unit
+) {
+  val colors = if (darkTheme) {
+    DarkColorPalette
+  } else {
+    LightColorPalette
+  }
+
+  MaterialTheme(
+    colors = colors,
+    typography = Typography,
+    shapes = Shapes,
+    content = content
+  )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
new file mode 100644
index 0000000..d8fb01c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Type.kt
@@ -0,0 +1,56 @@
+package com.android.credentialmanager.ui.theme
+
+import androidx.compose.material.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+  subtitle1 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 24.sp,
+    lineHeight = 32.sp,
+  ),
+  body1 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+  ),
+  body2 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Normal,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+    color = textColorSecondary
+  ),
+  button = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Medium,
+    fontSize = 14.sp,
+    lineHeight = 20.sp,
+  ),
+  h6 = TextStyle(
+    fontFamily = FontFamily.Default,
+    fontWeight = FontWeight.Medium,
+    fontSize = 16.sp,
+    lineHeight = 24.sp,
+    color = textColorPrimary
+  ),
+
+  /* Other default text styles to override
+    button = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.W500,
+        fontSize = 14.sp
+    ),
+    caption = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 12.sp
+    )
+    */
+)
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/SpaEnvironment.kt
index 773e5f3..6fe88e1 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/SpaEnvironment.kt
@@ -54,7 +54,7 @@
     parameter: List<NamedNavArgument> = emptyList(),
     arguments: Bundle? = null
 ): SettingsPage {
-    return SettingsPage(
+    return SettingsPage.create(
         name = SppName.name,
         displayName = SppName.displayName,
         parameter = parameter,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index c8a4650..c68e918 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,14 +18,18 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -41,27 +45,36 @@
     override val name = SettingsPageProviderEnum.HOME.name
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = createSettingsPage(SettingsPageProviderEnum.HOME)
         return listOf(
-            PreferenceMainPageProvider.buildInjectEntry().build(),
-            ArgumentPageProvider.buildInjectEntry("foo")!!.build(),
-            SliderPageProvider.buildInjectEntry().build(),
-            SpinnerPageProvider.buildInjectEntry().build(),
-            SettingsPagerPageProvider.buildInjectEntry().build(),
-            FooterPageProvider.buildInjectEntry().build(),
-            IllustrationPageProvider.buildInjectEntry().build(),
-            CategoryPageProvider.buildInjectEntry().build(),
-            ActionButtonPageProvider.buildInjectEntry().build(),
+            PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
+            SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         HomeScaffold(title = stringResource(R.string.app_name)) {
             for (entry in buildEntry(arguments)) {
                 if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
-                    entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
+                    entry.UiLayout(
+                        mergeArguments(
+                            listOf(
+                                globalRuntimeArgs,
+                                ArgumentPageModel.buildArgument(intParam = 0)
+                            )
+                        )
+                    )
                 } else {
-                    entry.UiLayout()
+                    entry.UiLayout(globalRuntimeArgs)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index cc0a79a..9bf7ad8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,12 +18,15 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
+import com.android.settingslib.spa.framework.util.mergeArguments
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.widget.preference.Preference
@@ -98,12 +101,20 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
             for (entry in buildEntry(arguments)) {
-                if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
-                    entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
+                if (entry.toPage != null) {
+                    entry.UiLayout(
+                        mergeArguments(
+                            listOf(
+                                globalRuntimeArgs,
+                                ArgumentPageModel.buildNextArgument(arguments)
+                            )
+                        )
+                    )
                 } else {
-                    entry.UiLayout()
+                    entry.UiLayout(globalRuntimeArgs)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index e75d09b..ee2bde4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -22,7 +22,6 @@
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.BrowseActivity
 import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.PageModel
 import com.android.settingslib.spa.framework.compose.navigator
@@ -92,14 +91,12 @@
     private var arguments: Bundle? = null
     private var stringParam: String? = null
     private var intParam: Int? = null
-    private var highlightName: String? = null
 
     override fun initialize(arguments: Bundle?) {
         logMsg("init with args " + arguments.toString())
         this.arguments = arguments
         stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
         intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
-        highlightName = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
     }
 
     @Composable
@@ -133,7 +130,8 @@
             override val title = genPageTitle()
             override val summary = stateOf(summaryArray.joinToString(", "))
             override val onClick = navigator(
-                SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments))
+                SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 9fc736c..89dbcb1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.getRuntimeArguments
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.createSettingsPage
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
@@ -177,9 +178,10 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
+        val globalRuntimeArgs = remember { getRuntimeArguments(arguments) }
         RegularScaffold(title = PAGE_TITLE) {
             for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
+                entry.UiLayout(globalRuntimeArgs)
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index 9a525bf..a4713b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -35,7 +35,7 @@
 private const val TITLE = "Sample Category"
 
 object CategoryPageProvider : SettingsPageProvider {
-    override val name = "Spinner"
+    override val name = "Category"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
         return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
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 c4e88e1..c698d9c 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
@@ -153,7 +153,7 @@
                         "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
                     override val summary = pageWithEntry.page.formatArguments().toState()
                     override val onClick =
-                        navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id()}")
+                        navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
                 })
             }
         }
@@ -172,7 +172,7 @@
         val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
         val pageWithEntry = entryRepository.getPageWithEntry(id)!!
         RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") {
-            Text(text = "id = ${pageWithEntry.page.id()}")
+            Text(text = "id = ${pageWithEntry.page.id}")
             Text(text = pageWithEntry.page.formatArguments())
             Text(text = "Entry size: ${pageWithEntry.entries.size}")
             Preference(model = object : PreferenceModel {
@@ -206,7 +206,7 @@
                 override val title = entry.displayTitle()
                 override val summary =
                     "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
-                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id()}")
+                override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
             })
         }
     }
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 33f1eb5..d923c1c 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
@@ -38,8 +38,9 @@
  * For gallery, AuthorityPath = com.android.spa.gallery.provider
  * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider
  * Some examples:
- *   $ adb shell content query --uri content://<AuthorityPath>/page_start
+ *   $ adb shell content query --uri content://<AuthorityPath>/page_debug
  *   $ 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,
@@ -50,6 +51,7 @@
      * Enum to define all column names in provider.
      */
     enum class ColumnEnum(val id: String) {
+        // Columns related to page
         PAGE_ID("pageId"),
         PAGE_NAME("pageName"),
         PAGE_ROUTE("pageRoute"),
@@ -57,6 +59,13 @@
         PAGE_ENTRY_COUNT("entryCount"),
         HAS_RUNTIME_PARAM("hasRuntimeParam"),
         PAGE_START_ADB("pageStartAdb"),
+
+        // Columns related to entry
+        ENTRY_ID("entryId"),
+        ENTRY_NAME("entryName"),
+        ENTRY_ROUTE("entryRoute"),
+        ENTRY_TITLE("entryTitle"),
+        ENTRY_SEARCH_KEYWORD("entrySearchKw"),
     }
 
     /**
@@ -67,12 +76,15 @@
         val queryMatchCode: Int,
         val columnNames: List<ColumnEnum>
     ) {
-        PAGE_START_COMMAND(
-            "page_start", 1,
+        // For debug
+        PAGE_DEBUG_QUERY(
+            "page_debug", 1,
             listOf(ColumnEnum.PAGE_START_ADB)
         ),
+
+        // page related queries.
         PAGE_INFO_QUERY(
-            "page_info", 2,
+            "page_info", 100,
             listOf(
                 ColumnEnum.PAGE_ID,
                 ColumnEnum.PAGE_NAME,
@@ -82,9 +94,24 @@
                 ColumnEnum.HAS_RUNTIME_PARAM,
             )
         ),
+
+        // entry related queries
+        ENTRY_INFO_QUERY(
+            "entry_info", 200,
+            listOf(
+                ColumnEnum.ENTRY_ID,
+                ColumnEnum.ENTRY_NAME,
+                ColumnEnum.ENTRY_ROUTE,
+                ColumnEnum.ENTRY_TITLE,
+                ColumnEnum.ENTRY_SEARCH_KEYWORD,
+            )
+        )
     }
 
-    private var uriMatcher: UriMatcher? = null
+    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
+    private fun addUri(authority: String, query: QueryEnum) {
+        uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode)
+    }
 
     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
         TODO("Implement this to handle requests to delete one or more rows")
@@ -115,18 +142,10 @@
     }
 
     override fun attachInfo(context: Context?, info: ProviderInfo?) {
-        uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
         if (info != null) {
-            uriMatcher!!.addURI(
-                info.authority,
-                QueryEnum.PAGE_START_COMMAND.queryPath,
-                QueryEnum.PAGE_START_COMMAND.queryMatchCode
-            )
-            uriMatcher!!.addURI(
-                info.authority,
-                QueryEnum.PAGE_INFO_QUERY.queryPath,
-                QueryEnum.PAGE_INFO_QUERY.queryMatchCode
-            )
+            addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY)
+            addUri(info.authority, QueryEnum.PAGE_INFO_QUERY)
+            addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY)
         }
         super.attachInfo(context, info)
     }
@@ -139,9 +158,10 @@
         sortOrder: String?
     ): Cursor? {
         return try {
-            when (uriMatcher!!.match(uri)) {
-                QueryEnum.PAGE_START_COMMAND.queryMatchCode -> queryPageStartCommand()
+            when (uriMatcher.match(uri)) {
+                QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug()
                 QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+                QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo()
                 else -> throw UnsupportedOperationException("Unknown Uri $uri")
             }
         } catch (e: UnsupportedOperationException) {
@@ -152,8 +172,8 @@
         }
     }
 
-    private fun queryPageStartCommand(): Cursor {
-        val cursor = MatrixCursor(QueryEnum.PAGE_START_COMMAND.getColumns())
+    private fun queryPageDebug(): Cursor {
+        val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val command = createBrowsePageAdbCommand(pageWithEntry.page)
             if (command != null) {
@@ -168,7 +188,7 @@
         for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
             val page = pageWithEntry.page
             cursor.newRow()
-                .add(ColumnEnum.PAGE_ID.id, page.id())
+                .add(ColumnEnum.PAGE_ID.id, page.id)
                 .add(ColumnEnum.PAGE_NAME.id, page.displayName)
                 .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute())
                 .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size)
@@ -181,6 +201,24 @@
         return cursor
     }
 
+    private fun queryEntryInfo(): Cursor {
+        val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
+        for (entry in entryRepository.getAllEntries()) {
+            // We can add runtime arguments if necessary
+            val searchData = entry.getSearchData()
+            cursor.newRow()
+                .add(ColumnEnum.ENTRY_ID.id, entry.id)
+                .add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
+                .add(ColumnEnum.ENTRY_ROUTE.id, entry.buildRoute())
+                .add(ColumnEnum.ENTRY_TITLE.id, searchData?.title ?: "")
+                .add(
+                    ColumnEnum.ENTRY_SEARCH_KEYWORD.id,
+                    searchData?.keyword ?: emptyList<String>()
+                )
+        }
+        return cursor
+    }
+
     private fun createBrowsePageIntent(page: SettingsPage): Intent {
         if (context == null || browseActivityClass == null || page.hasRuntimeParam())
             return Intent()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 2239066..07a6d6c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -17,8 +17,13 @@
 package com.android.settingslib.spa.framework.common
 
 import android.os.Bundle
+import android.widget.Toast
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.framework.BrowseActivity.Companion.HIGHLIGHT_ENTRY_PARAM_NAME
 
 const val INJECT_ENTRY_NAME = "INJECT"
 const val ROOT_ENTRY_NAME = "ROOT"
@@ -27,6 +32,9 @@
  * Defines data of a Settings entry.
  */
 data class SettingsEntry(
+    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+    val id: String,
+
     // The name of the page, which is used to compute the unique id, and need to be stable.
     private val name: String,
 
@@ -67,14 +75,9 @@
      */
     private val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
 ) {
-    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
-    fun id(): String {
-        return "$name:${owner.id()}(${fromPage?.id()}-${toPage?.id()})".toHashId()
-    }
-
     fun formatContent(): String {
         val content = listOf(
-            "id = ${id()}",
+            "id = $id",
             "owner = ${owner.formatDisplayTitle()}",
             "linkFrom = ${fromPage?.formatDisplayTitle()}",
             "linkTo = ${toPage?.formatDisplayTitle()}",
@@ -94,7 +97,7 @@
     }
 
     fun buildRoute(): String {
-        return containerPage().buildRoute(id())
+        return containerPage().buildRoute(id)
     }
 
     fun hasRuntimeParam(): Boolean {
@@ -104,6 +107,7 @@
     private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
         val arguments = Bundle()
         if (owner.arguments != null) arguments.putAll(owner.arguments)
+        // Put runtime args later, which can override page args.
         if (runtimeArguments != null) arguments.putAll(runtimeArguments)
         return arguments
     }
@@ -114,6 +118,15 @@
 
     @Composable
     fun UiLayout(runtimeArguments: Bundle? = null) {
+        val context = LocalContext.current
+        val highlight = rememberSaveable {
+            mutableStateOf(runtimeArguments?.getString(HIGHLIGHT_ENTRY_PARAM_NAME) == id)
+        }
+        if (highlight.value) {
+            highlight.value = false
+            // TODO: Add highlight entry logic
+            Toast.makeText(context, "entry $id highlighted", Toast.LENGTH_SHORT).show()
+        }
         uiLayoutImpl(fullArgument(runtimeArguments))
     }
 }
@@ -135,6 +148,7 @@
 
     fun build(): SettingsEntry {
         return SettingsEntry(
+            id = id(),
             name = name,
             owner = owner,
             displayName = displayName,
@@ -190,6 +204,11 @@
         return this
     }
 
+    // The unique id of this entry, which is computed by name + owner + fromPage + toPage.
+    private fun id(): String {
+        return "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toHashId()
+    }
+
     companion object {
         fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder {
             return SettingsEntryBuilder(entryName, owner)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index 77f064d..b6f6203 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -44,29 +44,23 @@
         val entryQueue = LinkedList<SettingsEntry>()
         for (page in sppRepository.getAllRootPages()) {
             val rootEntry = SettingsEntryBuilder.createRoot(owner = page).build()
-            val rootEntryId = rootEntry.id()
-            if (!entryMap.containsKey(rootEntryId)) {
+            if (!entryMap.containsKey(rootEntry.id)) {
                 entryQueue.push(rootEntry)
-                entryMap.put(rootEntryId, rootEntry)
+                entryMap.put(rootEntry.id, rootEntry)
             }
         }
 
         while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) {
             val entry = entryQueue.pop()
             val page = entry.toPage
-            val pageId = page?.id()
-            if (pageId == null || pageWithEntryMap.containsKey(pageId)) continue
+            if (page == null || pageWithEntryMap.containsKey(page.id)) continue
             val spp = sppRepository.getProviderOrNull(page.name) ?: continue
-            val newEntries = spp.buildEntry(page.arguments).map {
-                // Set from-page if it is missing.
-                if (it.fromPage == null) it.copy(fromPage = page) else it
-            }
-            pageWithEntryMap[pageId] = SettingsPageWithEntry(page, newEntries)
+            val newEntries = spp.buildEntry(page.arguments)
+            pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries)
             for (newEntry in newEntries) {
-                val newEntryId = newEntry.id()
-                if (!entryMap.containsKey(newEntryId)) {
+                if (!entryMap.containsKey(newEntry.id)) {
                     entryQueue.push(newEntry)
-                    entryMap.put(newEntryId, newEntry)
+                    entryMap.put(newEntry.id, newEntry)
                 }
             }
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 124743a..0c301b9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -26,6 +26,9 @@
  * Defines data to identify a Settings page.
  */
 data class SettingsPage(
+    // The unique id of this page, which is computed by name + normalized(arguments)
+    val id: String,
+
     // The name of the page, which is used to compute the unique id, and need to be stable.
     val name: String,
 
@@ -41,17 +44,28 @@
     companion object {
         fun create(
             name: String,
+            displayName: String? = null,
             parameter: List<NamedNavArgument> = emptyList(),
             arguments: Bundle? = null
         ): SettingsPage {
-            return SettingsPage(name, name, parameter, arguments)
+            return SettingsPage(
+                id = id(name, parameter, arguments),
+                name = name,
+                displayName = displayName ?: name,
+                parameter = parameter,
+                arguments = arguments
+            )
         }
-    }
 
-    // The unique id of this page, which is computed by name + normalized(arguments)
-    fun id(): String {
-        val normArguments = parameter.normalize(arguments)
-        return "$name:${normArguments?.toString()}".toHashId()
+        // The unique id of this page, which is computed by name + normalized(arguments)
+        private fun id(
+            name: String,
+            parameter: List<NamedNavArgument> = emptyList(),
+            arguments: Bundle? = null
+        ): String {
+            val normArguments = parameter.normalize(arguments)
+            return "$name:${normArguments?.toString()}".toHashId()
+        }
     }
 
     // Returns if this Settings Page is created by the given Spp.
@@ -69,12 +83,12 @@
         return "$displayName ${formatArguments()}"
     }
 
-    fun buildRoute(highlightEntryName: String? = null): String {
+    fun buildRoute(highlightEntryId: String? = null): String {
         val highlightParam =
-            if (highlightEntryName == null)
+            if (highlightEntryId == null)
                 ""
             else
-                "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryName"
+                "?${BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME}=$highlightEntryId"
         return name + parameter.navLink(arguments) + highlightParam
     }
 
@@ -114,5 +128,5 @@
 }
 
 fun String.toHashId(): String {
-    return this.hashCode().toString(36)
+    return this.hashCode().toUInt().toString(36)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index d7d7750..452f76a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavType
+import com.android.settingslib.spa.framework.BrowseActivity
 
 fun List<NamedNavArgument>.navRoute(): String {
     return this.joinToString("") { argument -> "/{${argument.name}}" }
@@ -70,3 +71,21 @@
     }
     return false
 }
+
+fun getRuntimeArguments(arguments: Bundle? = null): Bundle {
+    val res = Bundle()
+    val highlightEntry = arguments?.getString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME)
+    if (highlightEntry != null) {
+        res.putString(BrowseActivity.HIGHLIGHT_ENTRY_PARAM_NAME, highlightEntry)
+    }
+    // Append more general runtime arguments here
+    return res
+}
+
+fun mergeArguments(argsList: List<Bundle?>): Bundle {
+    val res = Bundle()
+    for (args in argsList) {
+        if (args != null) res.putAll(args)
+    }
+    return res
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 80b2364..883eddf 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -63,7 +63,7 @@
     override val parameter = PAGE_PARAMETER
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name, parameter, arguments)
+        val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
@@ -108,7 +108,10 @@
 
         fun buildPageData(permissionType: String): SettingsPage {
             return SettingsPage.create(
-                PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+                name = PAGE_NAME,
+                parameter = PAGE_PARAMETER,
+                arguments = bundleOf(PERMISSION to permissionType)
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index bd60bd36..ec7d75e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -63,7 +63,7 @@
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         val permissionType = parameter.getStringArg(PERMISSION, arguments)!!
-        val appListPage = SettingsPage.create(name, parameter, arguments)
+        val appListPage = SettingsPage.create(name, parameter = parameter, arguments = arguments)
         val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType)
         val entryList = mutableListOf<SettingsEntry>()
         // TODO: add more categories, such as personal, work, cloned, etc.
@@ -117,7 +117,10 @@
             listModelSupplier: (Context) -> TogglePermissionAppListModel<out AppRecord>,
         ): SettingsEntryBuilder {
             val appListPage = SettingsPage.create(
-                PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType))
+                name = PAGE_NAME,
+                parameter = PAGE_PARAMETER,
+                arguments = bundleOf(PERMISSION to permissionType)
+            )
             return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
                 .setUiLayoutFn {
                     val listModel = rememberContext(listModelSupplier)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 123c01b..79fb566 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -210,13 +210,15 @@
 
     LocalBluetoothLeBroadcast(Context context) {
         mExecutor = Executors.newSingleThreadExecutor();
-        BluetoothAdapter.getDefaultAdapter().
-                getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
         mBuilder = new BluetoothLeAudioContentMetadata.Builder();
         mContentResolver = context.getContentResolver();
         Handler handler = new Handler(Looper.getMainLooper());
         mSettingsObserver = new BroadcastSettingsObserver(handler);
         updateBroadcastInfoFromContentProvider();
+
+        // Before registering callback, the constructor should finish creating the all of variables.
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
     }
 
     /**
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f05f637..b1979c9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -768,6 +768,7 @@
                  Settings.Secure.SMS_DEFAULT_APPLICATION,
                  Settings.Secure.SPELL_CHECKER_ENABLED,  // Intentionally removed in Q
                  Settings.Secure.TRUST_AGENTS_INITIALIZED,
+                 Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
                  Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
                  Settings.Secure.TV_INPUT_CUSTOM_LABELS,
                  Settings.Secure.TV_INPUT_HIDDEN_INPUTS,
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
new file mode 100644
index 0000000..eb160de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_off.xml
@@ -0,0 +1,99 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="133"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="133"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="150"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
new file mode 100644
index 0000000..de972a6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_on.xml
@@ -0,0 +1,110 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="167"
+                    android:valueFrom="0.3"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="433"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
new file mode 100644
index 0000000..e33b264
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_hotspot_icon_search.xml
@@ -0,0 +1,132 @@
+<?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.
+  -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt">
+    <target android:name="_R_G_L_0_G_D_1_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="250"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_0_G_D_2_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="167"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="0"
+                    android:valueFrom="0.3"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="167"
+                    android:valueFrom="0.3"
+                    android:valueTo="1"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+                <objectAnimator
+                    android:duration="250"
+                    android:propertyName="fillAlpha"
+                    android:startOffset="417"
+                    android:valueFrom="1"
+                    android:valueTo="0.3"
+                    android:valueType="floatType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="time_group">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator
+                    android:duration="850"
+                    android:propertyName="translateX"
+                    android:startOffset="0"
+                    android:valueFrom="0"
+                    android:valueTo="1"
+                    android:valueType="floatType" />
+            </set>
+        </aapt:attr>
+    </target>
+    <aapt:attr name="android:drawable">
+        <vector
+            android:width="24dp"
+            android:height="24dp"
+            android:viewportHeight="24"
+            android:viewportWidth="24">
+            <group android:name="_R_G">
+                <group
+                    android:name="_R_G_L_0_G"
+                    android:translateX="12"
+                    android:translateY="12">
+                    <path
+                        android:name="_R_G_L_0_G_D_0_P_0"
+                        android:fillAlpha="1"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -1 C-1.1,-1 -2,-0.1 -2,1 C-2,1.55 -1.77,2.05 -1.41,2.41 C-1.05,2.77 -0.55,3 0,3 C0.55,3 1.05,2.77 1.41,2.41 C1.77,2.05 2,1.55 2,1 C2,-0.1 1.1,-1 0,-1c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_1_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -5 C-3.31,-5 -6,-2.31 -6,1 C-6,2.66 -5.32,4.15 -4.24,5.24 C-4.24,5.24 -2.82,3.82 -2.82,3.82 C-3.55,3.1 -4,2.11 -4,1 C-4,-1.21 -2.21,-3 0,-3 C2.21,-3 4,-1.21 4,1 C4,2.11 3.55,3.1 2.82,3.82 C2.82,3.82 4.24,5.24 4.24,5.24 C5.32,4.15 6,2.66 6,1 C6,-2.31 3.31,-5 0,-5c " />
+                    <path
+                        android:name="_R_G_L_0_G_D_2_P_0"
+                        android:fillAlpha="0.3"
+                        android:fillColor="#ffffff"
+                        android:fillType="nonZero"
+                        android:pathData=" M0 -9 C-5.52,-9 -10,-4.52 -10,1 C-10,3.76 -8.88,6.26 -7.07,8.07 C-7.07,8.07 -5.66,6.66 -5.66,6.66 C-7.1,5.21 -8,3.21 -8,1 C-8,-3.42 -4.42,-7 0,-7 C4.42,-7 8,-3.42 8,1 C8,3.21 7.1,5.2 5.65,6.65 C5.65,6.65 7.06,8.06 7.06,8.06 C8.88,6.26 10,3.76 10,1 C10,-4.52 5.52,-9 0,-9c " />
+                </group>
+            </group>
+            <group android:name="time_group" />
+        </vector>
+    </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index acb47f7..2d67d95 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -14,8 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
 -->
-<com.android.systemui.dreams.complication.DoubleShadowTextClock
+<com.android.systemui.shared.shadow.DoubleShadowTextClock
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/time_view"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
@@ -25,4 +26,13 @@
     android:format24Hour="@string/dream_time_complication_24_hr_time_format"
     android:fontFeatureSettings="pnum, lnum"
     android:letterSpacing="0.02"
-    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
+    android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"
+    app:keyShadowBlur="@dimen/dream_overlay_clock_key_text_shadow_radius"
+    app:keyShadowOffsetX="@dimen/dream_overlay_clock_key_text_shadow_dx"
+    app:keyShadowOffsetY="@dimen/dream_overlay_clock_key_text_shadow_dy"
+    app:keyShadowAlpha="0.3"
+    app:ambientShadowBlur="@dimen/dream_overlay_clock_ambient_text_shadow_radius"
+    app:ambientShadowOffsetX="@dimen/dream_overlay_clock_ambient_text_shadow_dx"
+    app:ambientShadowOffsetY="@dimen/dream_overlay_clock_ambient_text_shadow_dy"
+    app:ambientShadowAlpha="0.3"
+/>
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
index f9d66ee..96a5840 100644
--- a/packages/SystemUI/shared/res/values/attrs.xml
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -25,4 +25,39 @@
         <attr name="lockScreenWeight" format="integer" />
         <attr name="chargeAnimationDelay" format="integer" />
     </declare-styleable>
+
+    <declare-styleable name="DoubleShadowAttrDeclare">
+        <attr name="keyShadowBlur" format="dimension" />
+        <attr name="keyShadowOffsetX" format="dimension" />
+        <attr name="keyShadowOffsetY" format="dimension" />
+        <attr name="keyShadowAlpha" format="float" />
+        <attr name="ambientShadowBlur" format="dimension" />
+        <attr name="ambientShadowOffsetX" format="dimension" />
+        <attr name="ambientShadowOffsetY" format="dimension" />
+        <attr name="ambientShadowAlpha" format="float" />
+    </declare-styleable>
+
+    <declare-styleable name="DoubleShadowTextClock">
+        <attr name="keyShadowBlur" />
+        <attr name="keyShadowOffsetX" />
+        <attr name="keyShadowOffsetY" />
+        <attr name="keyShadowAlpha" />
+        <attr name="ambientShadowBlur" />
+        <attr name="ambientShadowOffsetX" />
+        <attr name="ambientShadowOffsetY" />
+        <attr name="ambientShadowAlpha" />
+    </declare-styleable>
+
+    <declare-styleable name="DoubleShadowTextView">
+        <attr name="keyShadowBlur" />
+        <attr name="keyShadowOffsetX" />
+        <attr name="keyShadowOffsetY" />
+        <attr name="keyShadowAlpha" />
+        <attr name="ambientShadowBlur" />
+        <attr name="ambientShadowOffsetX" />
+        <attr name="ambientShadowOffsetY" />
+        <attr name="ambientShadowAlpha" />
+        <attr name="drawableIconSize" format="dimension" />
+        <attr name="drawableIconInsetSize" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
new file mode 100644
index 0000000..3748eba
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowIconDrawable.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.systemui.shared.shadow
+
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.RenderEffect
+import android.graphics.RenderNode
+import android.graphics.Shader
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.InsetDrawable
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+
+/** A component to draw an icon with two layers of shadows. */
+class DoubleShadowIconDrawable(
+    keyShadowInfo: ShadowInfo,
+    ambientShadowInfo: ShadowInfo,
+    iconDrawable: Drawable,
+    iconSize: Int,
+    val iconInsetSize: Int
+) : Drawable() {
+    private val mAmbientShadowInfo: ShadowInfo
+    private val mCanvasSize: Int
+    private val mKeyShadowInfo: ShadowInfo
+    private val mIconDrawable: InsetDrawable
+    private val mDoubleShadowNode: RenderNode
+
+    init {
+        mCanvasSize = iconSize + iconInsetSize * 2
+        mKeyShadowInfo = keyShadowInfo
+        mAmbientShadowInfo = ambientShadowInfo
+        setBounds(0, 0, mCanvasSize, mCanvasSize)
+        mIconDrawable = InsetDrawable(iconDrawable, iconInsetSize)
+        mIconDrawable.setBounds(0, 0, mCanvasSize, mCanvasSize)
+        mDoubleShadowNode = createShadowRenderNode()
+    }
+
+    private fun createShadowRenderNode(): RenderNode {
+        val renderNode = RenderNode("DoubleShadowNode")
+        renderNode.setPosition(0, 0, mCanvasSize, mCanvasSize)
+        // Create render effects
+        val ambientShadow =
+            createShadowRenderEffect(
+                mAmbientShadowInfo.blur,
+                mAmbientShadowInfo.offsetX,
+                mAmbientShadowInfo.offsetY,
+                mAmbientShadowInfo.alpha
+            )
+        val keyShadow =
+            createShadowRenderEffect(
+                mKeyShadowInfo.blur,
+                mKeyShadowInfo.offsetX,
+                mKeyShadowInfo.offsetY,
+                mKeyShadowInfo.alpha
+            )
+        val blend = RenderEffect.createBlendModeEffect(ambientShadow, keyShadow, BlendMode.DARKEN)
+        renderNode.setRenderEffect(blend)
+        return renderNode
+    }
+
+    private fun createShadowRenderEffect(
+        radius: Float,
+        offsetX: Float,
+        offsetY: Float,
+        alpha: Float
+    ): RenderEffect {
+        return RenderEffect.createColorFilterEffect(
+            PorterDuffColorFilter(Color.argb(alpha, 0f, 0f, 0f), PorterDuff.Mode.MULTIPLY),
+            RenderEffect.createOffsetEffect(
+                offsetX,
+                offsetY,
+                RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
+            )
+        )
+    }
+
+    override fun draw(canvas: Canvas) {
+        if (canvas.isHardwareAccelerated) {
+            if (!mDoubleShadowNode.hasDisplayList()) {
+                // Record render node if its display list is not recorded or discarded
+                // (which happens when it's no longer drawn by anything).
+                val recordingCanvas = mDoubleShadowNode.beginRecording()
+                mIconDrawable.draw(recordingCanvas)
+                mDoubleShadowNode.endRecording()
+            }
+            canvas.drawRenderNode(mDoubleShadowNode)
+        }
+        mIconDrawable.draw(canvas)
+    }
+
+    override fun getOpacity(): Int {
+        return PixelFormat.TRANSPARENT
+    }
+
+    override fun setAlpha(alpha: Int) {
+        mIconDrawable.alpha = alpha
+    }
+
+    override fun setColorFilter(colorFilter: ColorFilter?) {
+        mIconDrawable.colorFilter = colorFilter
+    }
+
+    override fun setTint(color: Int) {
+        mIconDrawable.setTint(color)
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
new file mode 100644
index 0000000..f2db129
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextClock.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.systemui.shared.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.util.AttributeSet
+import android.widget.TextClock
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextClock] which draws two shadows on the text (ambient and key shadows) */
+class DoubleShadowTextClock
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextClock(context, attrs, defStyleAttr, defStyleRes) {
+    private val mAmbientShadowInfo: ShadowInfo
+    private val mKeyShadowInfo: ShadowInfo
+
+    init {
+        val attributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.DoubleShadowTextClock,
+                defStyleAttr,
+                defStyleRes
+            )
+        try {
+            val keyShadowBlur =
+                attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextClock_keyShadowBlur, 0)
+            val keyShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_keyShadowOffsetX,
+                    0
+                )
+            val keyShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_keyShadowOffsetY,
+                    0
+                )
+            val keyShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextClock_keyShadowAlpha, 0f)
+            mKeyShadowInfo =
+                ShadowInfo(
+                    keyShadowBlur.toFloat(),
+                    keyShadowOffsetX.toFloat(),
+                    keyShadowOffsetY.toFloat(),
+                    keyShadowAlpha
+                )
+            val ambientShadowBlur =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowBlur,
+                    0
+                )
+            val ambientShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowOffsetX,
+                    0
+                )
+            val ambientShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextClock_ambientShadowOffsetY,
+                    0
+                )
+            val ambientShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextClock_ambientShadowAlpha, 0f)
+            mAmbientShadowInfo =
+                ShadowInfo(
+                    ambientShadowBlur.toFloat(),
+                    ambientShadowOffsetX.toFloat(),
+                    ambientShadowOffsetY.toFloat(),
+                    ambientShadowAlpha
+                )
+        } finally {
+            attributes.recycle()
+        }
+    }
+
+    public override fun onDraw(canvas: Canvas) {
+        applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
index b1dc5a2..eaac93d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextHelper.kt
@@ -14,31 +14,33 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams.complication
+package com.android.systemui.shared.shadow
 
 import android.graphics.Canvas
+import android.graphics.Color
 import android.widget.TextView
-import androidx.annotation.ColorInt
 
-class DoubleShadowTextHelper
-constructor(
-    private val keyShadowInfo: ShadowInfo,
-    private val ambientShadowInfo: ShadowInfo,
-) {
+object DoubleShadowTextHelper {
     data class ShadowInfo(
         val blur: Float,
         val offsetX: Float = 0f,
         val offsetY: Float = 0f,
-        @ColorInt val color: Int
+        val alpha: Float
     )
 
-    fun applyShadows(view: TextView, canvas: Canvas, onDrawCallback: () -> Unit) {
+    fun applyShadows(
+        keyShadowInfo: ShadowInfo,
+        ambientShadowInfo: ShadowInfo,
+        view: TextView,
+        canvas: Canvas,
+        onDrawCallback: () -> Unit
+    ) {
         // We enhance the shadow by drawing the shadow twice
         view.paint.setShadowLayer(
             ambientShadowInfo.blur,
             ambientShadowInfo.offsetX,
             ambientShadowInfo.offsetY,
-            ambientShadowInfo.color
+            Color.argb(ambientShadowInfo.alpha, 0f, 0f, 0f)
         )
         onDrawCallback()
         canvas.save()
@@ -53,7 +55,7 @@
             keyShadowInfo.blur,
             keyShadowInfo.offsetX,
             keyShadowInfo.offsetY,
-            keyShadowInfo.color
+            Color.argb(keyShadowInfo.alpha, 0f, 0f, 0f)
         )
         onDrawCallback()
         canvas.restore()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
new file mode 100644
index 0000000..25d2721
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/shadow/DoubleShadowTextView.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.systemui.shared.shadow
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.shared.R
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.ShadowInfo
+import com.android.systemui.shared.shadow.DoubleShadowTextHelper.applyShadows
+
+/** Extension of [TextView] which draws two shadows on the text (ambient and key shadows} */
+class DoubleShadowTextView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : TextView(context, attrs, defStyleAttr, defStyleRes) {
+    private val mKeyShadowInfo: ShadowInfo
+    private val mAmbientShadowInfo: ShadowInfo
+
+    init {
+        val attributes =
+            context.obtainStyledAttributes(
+                attrs,
+                R.styleable.DoubleShadowTextView,
+                defStyleAttr,
+                defStyleRes
+            )
+        val drawableSize: Int
+        val drawableInsetSize: Int
+        try {
+            val keyShadowBlur =
+                attributes.getDimensionPixelSize(R.styleable.DoubleShadowTextView_keyShadowBlur, 0)
+            val keyShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_keyShadowOffsetX,
+                    0
+                )
+            val keyShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_keyShadowOffsetY,
+                    0
+                )
+            val keyShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextView_keyShadowAlpha, 0f)
+            mKeyShadowInfo =
+                ShadowInfo(
+                    keyShadowBlur.toFloat(),
+                    keyShadowOffsetX.toFloat(),
+                    keyShadowOffsetY.toFloat(),
+                    keyShadowAlpha
+                )
+            val ambientShadowBlur =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowBlur,
+                    0
+                )
+            val ambientShadowOffsetX =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowOffsetX,
+                    0
+                )
+            val ambientShadowOffsetY =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_ambientShadowOffsetY,
+                    0
+                )
+            val ambientShadowAlpha =
+                attributes.getFloat(R.styleable.DoubleShadowTextView_ambientShadowAlpha, 0f)
+            mAmbientShadowInfo =
+                ShadowInfo(
+                    ambientShadowBlur.toFloat(),
+                    ambientShadowOffsetX.toFloat(),
+                    ambientShadowOffsetY.toFloat(),
+                    ambientShadowAlpha
+                )
+            drawableSize =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_drawableIconSize,
+                    0
+                )
+            drawableInsetSize =
+                attributes.getDimensionPixelSize(
+                    R.styleable.DoubleShadowTextView_drawableIconInsetSize,
+                    0
+                )
+        } finally {
+            attributes.recycle()
+        }
+
+        val drawables = arrayOf<Drawable?>(null, null, null, null)
+        for ((index, drawable) in compoundDrawablesRelative.withIndex()) {
+            if (drawable == null) continue
+            drawables[index] =
+                DoubleShadowIconDrawable(
+                    mKeyShadowInfo,
+                    mAmbientShadowInfo,
+                    drawable,
+                    drawableSize,
+                    drawableInsetSize
+                )
+        }
+        setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3])
+    }
+
+    public override fun onDraw(canvas: Canvas) {
+        applyShadows(mKeyShadowInfo, mAmbientShadowInfo, this, canvas) { super.onDraw(canvas) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..1e5c53d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -24,6 +24,7 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.animation.Animator;
@@ -106,6 +107,8 @@
                 return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_password;
+            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+                return R.string.kg_prompt_reason_timeout_password;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 9871645..5b22324 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -330,6 +330,9 @@
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
                 break;
+            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+                mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
+                break;
             case PROMPT_REASON_NONE:
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c46e33d..0a91150 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -22,6 +22,7 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
+import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
 import android.animation.Animator;
@@ -123,6 +124,8 @@
                 return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT:
                 return R.string.kg_prompt_reason_timeout_pin;
+            case PROMPT_REASON_TRUSTAGENT_EXPIRED:
+                return R.string.kg_prompt_reason_timeout_pin;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index ac00e94..9d0a8ac 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -61,6 +61,12 @@
     int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
 
     /**
+     * Some auth is required because the trustagent expired either from timeout or manually by
+     * the user
+     */
+    int PROMPT_REASON_TRUSTAGENT_EXPIRED = 8;
+
+    /**
      * Reset the view and prepare to take input. This should do things like clearing the
      * password or pattern and clear error messages.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 7fc8123..a5fdc68 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -103,7 +103,6 @@
 import com.android.systemui.statusbar.phone.SystemUIDialogManager;
 import com.android.systemui.statusbar.policy.AccessibilityController;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BluetoothController;
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -253,7 +252,6 @@
     @Inject Lazy<UserInfoController> mUserInfoController;
     @Inject Lazy<KeyguardStateController> mKeyguardMonitor;
     @Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
-    @Inject Lazy<BatteryController> mBatteryController;
     @Inject Lazy<NightDisplayListener> mNightDisplayListener;
     @Inject Lazy<ReduceBrightColorsController> mReduceBrightColorsController;
     @Inject Lazy<ManagedProfileController> mManagedProfileController;
@@ -404,8 +402,6 @@
 
         mProviders.put(UserInfoController.class, mUserInfoController::get);
 
-        mProviders.put(BatteryController.class, mBatteryController::get);
-
         mProviders.put(NightDisplayListener.class, mNightDisplayListener::get);
 
         mProviders.put(ReduceBrightColorsController.class, mReduceBrightColorsController::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 2e13903..67b683e 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -455,7 +455,6 @@
                     }
                 }
 
-                boolean needToUpdateProviderViews = false;
                 final String newUniqueId = mDisplayInfo.uniqueId;
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
@@ -473,37 +472,6 @@
                         setupDecorations();
                         return;
                     }
-
-                    if (mScreenDecorHwcLayer != null) {
-                        updateHwLayerRoundedCornerDrawable();
-                        updateHwLayerRoundedCornerExistAndSize();
-                    }
-                    needToUpdateProviderViews = true;
-                }
-
-                final float newRatio = getPhysicalPixelDisplaySizeRatio();
-                if (mRoundedCornerResDelegate.getPhysicalPixelDisplaySizeRatio() != newRatio) {
-                    mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(newRatio);
-                    if (mScreenDecorHwcLayer != null) {
-                        updateHwLayerRoundedCornerExistAndSize();
-                    }
-                    needToUpdateProviderViews = true;
-                }
-
-                if (needToUpdateProviderViews) {
-                    updateOverlayProviderViews(null);
-                } else {
-                    updateOverlayProviderViews(new Integer[] {
-                            mFaceScanningViewId,
-                            R.id.display_cutout,
-                            R.id.display_cutout_left,
-                            R.id.display_cutout_right,
-                            R.id.display_cutout_bottom,
-                    });
-                }
-
-                if (mScreenDecorHwcLayer != null) {
-                    mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
                 }
             }
         };
@@ -1069,6 +1037,8 @@
                 && (newRotation != mRotation || displayModeChanged(mDisplayMode, newMod))) {
             mRotation = newRotation;
             mDisplayMode = newMod;
+            mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio(
+                    getPhysicalPixelDisplaySizeRatio());
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingConfigChange = false;
                 mScreenDecorHwcLayer.updateRotation(mRotation);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 56103773..1ceb6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -187,7 +187,10 @@
         public void onReceive(Context context, Intent intent) {
             if (mCurrentDialog != null
                     && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
+                String reason = intent.getStringExtra("reason");
+                reason = (reason != null) ? reason : "unknown";
+                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason);
+
                 mCurrentDialog.dismissWithoutCallback(true /* animate */);
                 mCurrentDialog = null;
 
@@ -370,18 +373,14 @@
 
     @Override
     public void onTryAgainPressed(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onTryAgainPressed: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onTryAgainPressed");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onTryAgainPressed");
             return;
         }
 
         try {
-            mReceiver.onTryAgainPressed();
+            receiver.onTryAgainPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling try again", e);
         }
@@ -389,18 +388,14 @@
 
     @Override
     public void onDeviceCredentialPressed(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onDeviceCredentialPressed: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onDeviceCredentialPressed");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onDeviceCredentialPressed");
             return;
         }
 
         try {
-            mReceiver.onDeviceCredentialPressed();
+            receiver.onDeviceCredentialPressed();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when handling credential button", e);
         }
@@ -408,18 +403,14 @@
 
     @Override
     public void onSystemEvent(int event, long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onSystemEvent");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onSystemEvent");
             return;
         }
 
         try {
-            mReceiver.onSystemEvent(event);
+            receiver.onSystemEvent(event);
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when sending system event", e);
         }
@@ -427,23 +418,46 @@
 
     @Override
     public void onDialogAnimatedIn(long requestId) {
-        if (mReceiver == null) {
-            Log.e(TAG, "onDialogAnimatedIn: Receiver is null");
-            return;
-        }
-
-        if (requestId != mCurrentDialog.getRequestId()) {
-            Log.w(TAG, "requestId doesn't match, skip onDialogAnimatedIn");
+        final IBiometricSysuiReceiver receiver = getCurrentReceiver(requestId);
+        if (receiver == null) {
+            Log.w(TAG, "Skip onDialogAnimatedIn");
             return;
         }
 
         try {
-            mReceiver.onDialogAnimatedIn();
+            receiver.onDialogAnimatedIn();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
         }
     }
 
+    @Nullable
+    private IBiometricSysuiReceiver getCurrentReceiver(long requestId) {
+        if (!isRequestIdValid(requestId)) {
+            return null;
+        }
+
+        if (mReceiver == null) {
+            Log.w(TAG, "getCurrentReceiver: Receiver is null");
+        }
+
+        return mReceiver;
+    }
+
+    private boolean isRequestIdValid(long requestId) {
+        if (mCurrentDialog == null) {
+            Log.w(TAG, "shouldNotifyReceiver: dialog already gone");
+            return false;
+        }
+
+        if (requestId != mCurrentDialog.getRequestId()) {
+            Log.w(TAG, "shouldNotifyReceiver: requestId doesn't match");
+            return false;
+        }
+
+        return true;
+    }
+
     @Override
     public void onDismissed(@DismissedReason int reason,
                             @Nullable byte[] credentialAttestation, long requestId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 36287f5..27e9af9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -342,8 +342,11 @@
             if (mOverlay != null
                     && mOverlay.getRequestReason() != REASON_AUTH_KEYGUARD
                     && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
-                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: "
-                        + mOverlay.getRequestReason());
+                String reason = intent.getStringExtra("reason");
+                reason = (reason != null) ? reason : "unknown";
+                Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, reason: " + reason
+                        + ", mRequestReason: " + mOverlay.getRequestReason());
+
                 mOverlay.cancel();
                 hideUdfpsOverlay();
             }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index fbfc94a..a996699 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -35,6 +35,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -126,6 +127,7 @@
             PowerManager powerManager,
             BroadcastDispatcher broadcastDispatcher,
             DemoModeController demoModeController,
+            DumpManager dumpManager,
             @Main Handler mainHandler,
             @Background Handler bgHandler) {
         BatteryController bC = new BatteryControllerImpl(
@@ -134,6 +136,7 @@
                 powerManager,
                 broadcastDispatcher,
                 demoModeController,
+                dumpManager,
                 mainHandler,
                 bgHandler);
         bC.init();
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index a252864..8b4aeef 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -78,23 +78,18 @@
         reloadMeasures()
     }
 
-    private fun reloadAll(newReloadToken: Int) {
-        if (reloadToken == newReloadToken) {
-            return
-        }
-        reloadToken = newReloadToken
-        reloadRes()
-        reloadMeasures()
-    }
-
     fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
         if (displayUniqueId != newDisplayUniqueId) {
             displayUniqueId = newDisplayUniqueId
             newReloadToken ?.let { reloadToken = it }
             reloadRes()
             reloadMeasures()
-        } else {
-            newReloadToken?.let { reloadAll(it) }
+        } else if (newReloadToken != null) {
+            if (reloadToken == newReloadToken) {
+                return
+            }
+            reloadToken = newReloadToken
+            reloadMeasures()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
deleted file mode 100644
index 789ebc5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextClock.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.systemui.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextClock;
-
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.DoubleShadowTextHelper.ShadowInfo;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextClock} which draws two shadows on the text (ambient and key shadows)
- */
-public class DoubleShadowTextClock extends TextClock {
-    private final DoubleShadowTextHelper mShadowHelper;
-
-    public DoubleShadowTextClock(Context context) {
-        this(context, null);
-    }
-
-    public DoubleShadowTextClock(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DoubleShadowTextClock(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        final Resources resources = context.getResources();
-        final ShadowInfo keyShadowInfo = new ShadowInfo(
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_radius),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dx),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_key_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_clock_key_text_shadow_color));
-
-        final ShadowInfo ambientShadowInfo = new ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_clock_ambient_text_shadow_radius),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dx),
-                resources.getDimensionPixelSize(R.dimen.dream_overlay_clock_ambient_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_clock_ambient_text_shadow_color));
-        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        mShadowHelper.applyShadows(this, canvas, () -> {
-            super.onDraw(canvas);
-            return Unit.INSTANCE;
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
deleted file mode 100644
index cf7e312..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DoubleShadowTextView.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.systemui.dreams.complication;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-
-import kotlin.Unit;
-
-/**
- * Extension of {@link TextView} which draws two shadows on the text (ambient and key shadows}
- */
-public class DoubleShadowTextView extends TextView {
-    private final DoubleShadowTextHelper mShadowHelper;
-
-    public DoubleShadowTextView(Context context) {
-        this(context, null);
-    }
-
-    public DoubleShadowTextView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DoubleShadowTextView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-
-        final Resources resources = context.getResources();
-        final DoubleShadowTextHelper.ShadowInfo
-                keyShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_radius),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_dx),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_key_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_status_bar_key_text_shadow_color));
-
-        final DoubleShadowTextHelper.ShadowInfo
-                ambientShadowInfo = new DoubleShadowTextHelper.ShadowInfo(
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_radius),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dx),
-                resources.getDimensionPixelSize(
-                        R.dimen.dream_overlay_status_bar_ambient_text_shadow_dy),
-                resources.getColor(R.color.dream_overlay_status_bar_ambient_text_shadow_color));
-        mShadowHelper = new DoubleShadowTextHelper(keyShadowInfo, ambientShadowInfo);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        mShadowHelper.applyShadows(this, canvas, () -> {
-            super.onDraw(canvas);
-            return Unit.INSTANCE;
-        });
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 26db3ee4..8c4d17d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -24,6 +24,7 @@
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_UNLOCK_ANIMATION;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -802,6 +803,9 @@
             } else if (trustAgentsEnabled
                     && (strongAuth & SOME_AUTH_REQUIRED_AFTER_USER_REQUEST) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+            } else if (trustAgentsEnabled
+                    && (strongAuth & SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED) != 0) {
+                return KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
             } else if (any && ((strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0
                     || mUpdateMonitor.isFingerprintLockedOut())) {
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 5612c22..29e2c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -76,6 +76,14 @@
         return factory.create("NotifInterruptLog", 100);
     }
 
+    /** Provides a logging buffer for notification rendering events. */
+    @Provides
+    @SysUISingleton
+    @NotificationRenderLog
+    public static LogBuffer provideNotificationRenderLogBuffer(LogBufferFactory factory) {
+        return factory.create("NotifRenderLog", 100);
+    }
+
     /** Provides a logging buffer for all logs for lockscreen to shade transition events. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
new file mode 100644
index 0000000..8c8753a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationRenderLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification rendering logging. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationRenderLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 654c158..b36f33b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -202,6 +202,7 @@
      * It will be called when the container is out of view.
      */
     lateinit var updateUserVisibility: () -> Unit
+    lateinit var updateHostVisibility: () -> Unit
 
     private val isReorderingAllowed: Boolean
         get() = visualStabilityProvider.isReorderingAllowed
@@ -225,7 +226,13 @@
                 reorderAllPlayers(previousVisiblePlayerKey = null)
             }
 
-            keysNeedRemoval.forEach { removePlayer(it) }
+            keysNeedRemoval.forEach {
+                removePlayer(it)
+            }
+            if (keysNeedRemoval.size > 0) {
+                // Carousel visibility may need to be updated after late removals
+                updateHostVisibility()
+            }
             keysNeedRemoval.clear()
 
             // Update user visibility so that no extra impression will be logged when
@@ -247,6 +254,7 @@
                 receivedSmartspaceCardLatency: Int,
                 isSsReactivated: Boolean
             ) {
+                debugLogger.logMediaLoaded(key)
                 if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
                     // Log card received if a new resumable media card is added
                     MediaPlayerData.getMediaPlayer(key)?.let {
@@ -315,7 +323,7 @@
                 data: SmartspaceMediaData,
                 shouldPrioritize: Boolean
             ) {
-                if (DEBUG) Log.d(TAG, "Loading Smartspace media update")
+                debugLogger.logRecommendationLoaded(key)
                 // Log the case where the hidden media carousel with the existed inactive resume
                 // media is shown by the Smartspace signal.
                 if (data.isActive) {
@@ -370,13 +378,21 @@
             }
 
             override fun onMediaDataRemoved(key: String) {
+                debugLogger.logMediaRemoved(key)
                 removePlayer(key)
             }
 
             override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) {
-                if (DEBUG) Log.d(TAG, "My Smartspace media removal request is received")
+                debugLogger.logRecommendationRemoved(key, immediately)
                 if (immediately || isReorderingAllowed) {
-                    onMediaDataRemoved(key)
+                    removePlayer(key)
+                    if (!immediately) {
+                        // Although it wasn't requested, we were able to process the removal
+                        // immediately since reordering is allowed. So, notify hosts to update
+                        if (this@MediaCarouselController::updateHostVisibility.isInitialized) {
+                            updateHostVisibility()
+                        }
+                    }
                 } else {
                     keysNeedRemoval.add(key)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
index 04ebd5a..b1018f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselControllerLogger.kt
@@ -40,6 +40,37 @@
                     "Removing control panel for $str1 from map without calling #onDestroy"
         }
     )
+
+    fun logMediaLoaded(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        { str1 = key },
+        { "add player $str1" }
+    )
+
+    fun logMediaRemoved(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        { str1 = key },
+        { "removing player $str1" }
+    )
+
+    fun logRecommendationLoaded(key: String) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        { str1 = key },
+        { "add recommendation $str1" }
+    )
+
+    fun logRecommendationRemoved(key: String, immediately: Boolean) = buffer.log(
+        TAG,
+        LogLevel.DEBUG,
+        {
+            str1 = key
+            bool1 = immediately
+        },
+        { "removing recommendation $str1, immediate=$bool1" }
+    )
 }
 
 private const val TAG = "MediaCarouselCtlrLog"
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index c48271e..896fb47 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -1322,6 +1322,7 @@
             println("externalListeners: ${mediaDataFilter.listeners}")
             println("mediaEntries: $mediaEntries")
             println("useMediaResumption: $useMediaResumption")
+            println("allowMediaRecommendations: $allowMediaRecommendations")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 6baf6e1..e0b6d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -546,6 +546,11 @@
         mediaCarouselController.updateUserVisibility = {
             mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
         }
+        mediaCarouselController.updateHostVisibility = {
+            mediaHosts.forEach {
+                it?.updateViewVisibility()
+            }
+        }
 
         panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
             override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index de2b5c9..8645922 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -167,7 +167,11 @@
         }
     }
 
-    private fun updateViewVisibility() {
+    /**
+     * Updates this host's state based on the current media data's status, and invokes listeners if
+     * the visibility has changed
+     */
+    fun updateViewVisibility() {
         state.visible = if (showsOnlyActiveMedia) {
             mediaDataManager.hasActiveMediaOrRecommendation()
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 8e4ca5a..731e348 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -380,7 +380,7 @@
         type: TYPE
     ) = traceSection("MediaViewController#attach") {
         updateMediaViewControllerType(type)
-        logger.logMediaLocation("attach", currentStartLocation, currentEndLocation)
+        logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
         this.transitionLayout = transitionLayout
         layoutController.attach(transitionLayout)
         if (currentEndLocation == -1) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 7c4c64c..d605c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -955,9 +955,9 @@
             updateDisabledForQuickstep(newConfig);
         }
 
-        if (DEBUG_MISSING_GESTURE) {
-            Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig);
-        }
+        // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
+        Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+                + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
         updateDisplaySize();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index b6f6e93..624def6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -51,8 +51,6 @@
 /** Quick settings tile: Hotspot **/
 public class HotspotTile extends QSTileImpl<BooleanState> {
 
-    private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
-
     private final HotspotController mHotspotController;
     private final DataSaverController mDataSaverController;
 
@@ -129,9 +127,6 @@
     @Override
     protected void handleUpdateState(BooleanState state, Object arg) {
         final boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING;
-        if (state.slash == null) {
-            state.slash = new SlashState();
-        }
 
         final int numConnectedDevices;
         final boolean isTransient = transientEnabling || mHotspotController.isHotspotTransient();
@@ -150,13 +145,14 @@
             isDataSaverEnabled = mDataSaverController.isDataSaverEnabled();
         }
 
-        state.icon = mEnabledStatic;
         state.label = mContext.getString(R.string.quick_settings_hotspot_label);
         state.isTransient = isTransient;
-        state.slash.isSlashed = !state.value && !state.isTransient;
         if (state.isTransient) {
             state.icon = ResourceIcon.get(
-                    com.android.internal.R.drawable.ic_hotspot_transient_animation);
+                    R.drawable.qs_hotspot_icon_search);
+        } else {
+            state.icon = ResourceIcon.get(state.value
+                    ? R.drawable.qs_hotspot_icon_on : R.drawable.qs_hotspot_icon_off);
         }
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index f60e066..92f6690a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -81,8 +81,7 @@
         super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mBatteryController = batteryController;
-        mUiModeManager = (UiModeManager) host.getUserContext().getSystemService(
-                Context.UI_MODE_SERVICE);
+        mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
         mLocationController = locationController;
         configurationController.observe(getLifecycle(), this);
         batteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1011a6d..d7e86b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2230,7 +2230,8 @@
             if (cancel) {
                 collapse(false /* delayed */, 1.0f /* speedUpFactor */);
             } else {
-                maybeVibrateOnOpening();
+                // Window never will receive touch events that typically trigger haptic on open.
+                maybeVibrateOnOpening(false /* openingWithTouch */);
                 fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
             }
             onTrackingStopped(false);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index c3f1e57..b4ce95c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -96,6 +96,7 @@
     private float mMinExpandHeight;
     private boolean mPanelUpdateWhenAnimatorEnds;
     private final boolean mVibrateOnOpening;
+    private boolean mHasVibratedOnOpen = false;
     protected boolean mIsLaunchAnimationRunning;
     private int mFixedDuration = NO_FIXED_DURATION;
     protected float mOverExpansion;
@@ -353,8 +354,8 @@
 
     private void startOpening(MotionEvent event) {
         updatePanelExpansionAndVisibility();
-        maybeVibrateOnOpening();
-
+        // Reset at start so haptic can be triggered as soon as panel starts to open.
+        mHasVibratedOnOpen = false;
         //TODO: keyguard opens QS a different way; log that too?
 
         // Log the position of the swipe that opened the panel
@@ -368,9 +369,18 @@
                 .log(LockscreenUiEvent.LOCKSCREEN_UNLOCKED_NOTIFICATION_PANEL_EXPAND);
     }
 
-    protected void maybeVibrateOnOpening() {
+    /**
+     * Maybe vibrate as panel is opened.
+     *
+     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
+     * being opened programmatically (such as by the open panel gesture), we always play haptic.
+     */
+    protected void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+            if (!openingWithTouch || !mHasVibratedOnOpen) {
+                mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+                mHasVibratedOnOpen = true;
+            }
         }
     }
 
@@ -1371,6 +1381,9 @@
                     break;
                 case MotionEvent.ACTION_MOVE:
                     addMovement(event);
+                    if (!isFullyCollapsed()) {
+                        maybeVibrateOnOpening(true /* openingWithTouch */);
+                    }
                     float h = y - mInitialExpandY;
 
                     // If the panel was collapsed when touching, we only need to check for the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
new file mode 100644
index 0000000..fe03b2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.statusbar.notification.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationRenderLog
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationSection
+import javax.inject.Inject
+
+/** Handles logging for the {NotificationRoundnessManager}. */
+class NotificationRoundnessLogger
+@Inject
+constructor(@NotificationRenderLog val buffer: LogBuffer) {
+
+    /** Called when the {NotificationRoundnessManager} updates the corners if the Notifications. */
+    fun onCornersUpdated(
+        view: ExpandableView?,
+        isFirstInSection: Boolean,
+        isLastInSection: Boolean,
+        topChanged: Boolean,
+        bottomChanged: Boolean
+    ) {
+        buffer.log(
+            TAG_ROUNDNESS,
+            INFO,
+            {
+                str1 = (view as? ExpandableNotificationRow)?.entry?.key
+                bool1 = isFirstInSection
+                bool2 = isLastInSection
+                bool3 = topChanged
+                bool4 = bottomChanged
+            },
+            {
+                "onCornersUpdated: " +
+                    "entry=$str1 isFirstInSection=$bool1 isLastInSection=$bool2 " +
+                    "topChanged=$bool3 bottomChanged=$bool4"
+            }
+        )
+    }
+
+    /** Called when we update the {NotificationRoundnessManager} with new sections. */
+    fun onSectionCornersUpdated(sections: Array<NotificationSection?>, anyChanged: Boolean) {
+        buffer.log(
+            TAG_ROUNDNESS,
+            INFO,
+            {
+                int1 = sections.size
+                bool1 = anyChanged
+            },
+            { "onSectionCornersUpdated: sections size=$int1 anyChanged=$bool1" }
+        )
+    }
+}
+
+private const val TAG_ROUNDNESS = "NotifRoundnessLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index b589d9a..2015c87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -19,12 +19,19 @@
 import android.content.res.Resources;
 import android.util.MathUtils;
 
+import androidx.annotation.NonNull;
+
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
+import java.io.PrintWriter;
 import java.util.HashSet;
 
 import javax.inject.Inject;
@@ -33,12 +40,16 @@
  * A class that manages the roundness for notification views
  */
 @SysUISingleton
-public class NotificationRoundnessManager {
+public class NotificationRoundnessManager implements Dumpable {
+
+    private static final String TAG = "NotificationRoundnessManager";
 
     private final ExpandableView[] mFirstInSectionViews;
     private final ExpandableView[] mLastInSectionViews;
     private final ExpandableView[] mTmpFirstInSectionViews;
     private final ExpandableView[] mTmpLastInSectionViews;
+    private final NotificationRoundnessLogger mNotifLogger;
+    private final DumpManager mDumpManager;
     private boolean mExpanded;
     private HashSet<ExpandableView> mAnimatedChildren;
     private Runnable mRoundingChangedCallback;
@@ -53,12 +64,31 @@
 
     @Inject
     NotificationRoundnessManager(
-            NotificationSectionsFeatureManager sectionsFeatureManager) {
+            NotificationSectionsFeatureManager sectionsFeatureManager,
+            NotificationRoundnessLogger notifLogger,
+            DumpManager dumpManager) {
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
         mTmpFirstInSectionViews = new ExpandableView[numberOfSections];
         mTmpLastInSectionViews = new ExpandableView[numberOfSections];
+        mNotifLogger = notifLogger;
+        mDumpManager = dumpManager;
+
+        mDumpManager.registerDumpable(TAG, this);
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mFirstInSectionViews: length=" + mFirstInSectionViews.length);
+        pw.println(dumpViews(mFirstInSectionViews));
+        pw.println("mLastInSectionViews: length=" + mLastInSectionViews.length);
+        pw.println(dumpViews(mFirstInSectionViews));
+        if (mTrackedHeadsUp != null) {
+            pw.println("trackedHeadsUp=" + mTrackedHeadsUp.getEntry());
+        }
+        pw.println("roundForPulsingViews=" + mRoundForPulsingViews);
+        pw.println("isClearAllInProgress=" + mIsClearAllInProgress);
     }
 
     public void updateView(ExpandableView view, boolean animate) {
@@ -95,6 +125,9 @@
         view.setFirstInSection(isFirstInSection);
         view.setLastInSection(isLastInSection);
 
+        mNotifLogger.onCornersUpdated(view, isFirstInSection,
+                isLastInSection, topChanged, bottomChanged);
+
         return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
     }
 
@@ -184,6 +217,7 @@
         if (isLastInSection(view) && !top) {
             return 1.0f;
         }
+
         if (view == mTrackedHeadsUp) {
             // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
             // rounded.
@@ -220,6 +254,8 @@
         if (anyChanged) {
             mRoundingChangedCallback.run();
         }
+
+        mNotifLogger.onSectionCornersUpdated(sections, anyChanged);
     }
 
     private boolean handleRemovedOldViews(NotificationSection[] sections,
@@ -296,4 +332,36 @@
     public void setShouldRoundPulsingViews(boolean shouldRoundPulsingViews) {
         mRoundForPulsingViews = shouldRoundPulsingViews;
     }
+
+    private String dumpViews(ExpandableView[] views) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < views.length; i++) {
+            if (views[i] == null) continue;
+
+            sb.append("\t")
+                    .append("[").append(i).append("] ")
+                    .append("isPinned=").append(views[i].isPinned()).append(" ")
+                    .append("isFirstInSection=").append(views[i].isFirstInSection()).append(" ")
+                    .append("isLastInSection=").append(views[i].isLastInSection()).append(" ");
+
+            if (views[i] instanceof ExpandableNotificationRow) {
+                sb.append("entry=");
+                dumpEntry(((ExpandableNotificationRow) views[i]).getEntry(), sb);
+            }
+
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+
+    private void dumpEntry(NotificationEntry entry, StringBuilder sb) {
+        sb.append("NotificationEntry{key=").append(entry.getKey()).append(" ");
+
+        if (entry.getSection() != null) {
+            sb.append(" section=")
+                    .append(entry.getSection().getLabel());
+        }
+
+        sb.append("}");
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 753e940..149ed0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -19,14 +19,17 @@
 import android.annotation.Nullable;
 import android.view.View;
 
-import com.android.systemui.Dumpable;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 
-public interface BatteryController extends DemoMode, Dumpable,
+/**
+ * Controller for battery related information, including the charge level, power save mode,
+ * and time remaining information
+ */
+public interface BatteryController extends DemoMode,
         CallbackController<BatteryStateChangeCallback> {
     /**
      * Prints the current state of the {@link BatteryController} to the given {@link PrintWriter}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 33ddf7e..c7ad767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -38,11 +38,13 @@
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dumpable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.util.Assert;
 
@@ -56,7 +58,8 @@
  * Default implementation of a {@link BatteryController}. This controller monitors for battery
  * level change events that are broadcasted by the system.
  */
-public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
+public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController,
+        Dumpable {
     private static final String TAG = "BatteryController";
 
     private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
@@ -70,6 +73,7 @@
     private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
     private final PowerManager mPowerManager;
     private final DemoModeController mDemoModeController;
+    private final DumpManager mDumpManager;
     private final Handler mMainHandler;
     private final Handler mBgHandler;
     protected final Context mContext;
@@ -101,6 +105,7 @@
             PowerManager powerManager,
             BroadcastDispatcher broadcastDispatcher,
             DemoModeController demoModeController,
+            DumpManager dumpManager,
             @Main Handler mainHandler,
             @Background Handler bgHandler) {
         mContext = context;
@@ -110,6 +115,7 @@
         mEstimates = enhancedEstimates;
         mBroadcastDispatcher = broadcastDispatcher;
         mDemoModeController = demoModeController;
+        mDumpManager = dumpManager;
     }
 
     private void registerReceiver() {
@@ -134,6 +140,7 @@
             }
         }
         mDemoModeController.addCallback(this);
+        mDumpManager.registerDumpable(TAG, this);
         updatePowerSave();
         updateEstimateInBackground();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 27746c0..00ed3d6 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -36,6 +36,7 @@
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.doze.DozeHost;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.gestural.GestureModule;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -116,9 +117,12 @@
     static BatteryController provideBatteryController(Context context,
             EnhancedEstimates enhancedEstimates, PowerManager powerManager,
             BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController,
+            DumpManager dumpManager,
             @Main Handler mainHandler, @Background Handler bgHandler) {
         BatteryController bC = new BatteryControllerImpl(context, enhancedEstimates, powerManager,
-                broadcastDispatcher, demoModeController, mainHandler, bgHandler);
+                broadcastDispatcher, demoModeController,
+                dumpManager,
+                mainHandler, bgHandler);
         bC.init();
         return bC;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index df10dfe..5a26d05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1005,18 +1005,13 @@
         assertEquals(new Size(3, 3), resDelegate.getTopRoundedSize());
         assertEquals(new Size(4, 4), resDelegate.getBottomRoundedSize());
 
-        setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px)
-                /* roundedTopDrawable */,
-                getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px)
-                /* roundedBottomDrawable */,
-                0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/);
+        doReturn(2f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio();
         mDisplayInfo.rotation = Surface.ROTATION_270;
 
         mScreenDecorations.onConfigurationChanged(null);
 
-        assertEquals(new Size(4, 4), resDelegate.getTopRoundedSize());
-        assertEquals(new Size(5, 5), resDelegate.getBottomRoundedSize());
+        assertEquals(new Size(6, 6), resDelegate.getTopRoundedSize());
+        assertEquals(new Size(8, 8), resDelegate.getBottomRoundedSize());
     }
 
     @Test
@@ -1293,51 +1288,6 @@
     }
 
     @Test
-    public void testOnDisplayChanged_hwcLayer() {
-        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-        final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport();
-        decorationSupport.format = PixelFormat.R_8;
-        doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport();
-
-        // top cutout
-        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
-        mScreenDecorations.start();
-
-        final ScreenDecorHwcLayer hwcLayer = mScreenDecorations.mScreenDecorHwcLayer;
-        spyOn(hwcLayer);
-        doReturn(mDisplay).when(hwcLayer).getDisplay();
-
-        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
-        verify(hwcLayer, times(1)).onDisplayChanged(any());
-    }
-
-    @Test
-    public void testOnDisplayChanged_nonHwcLayer() {
-        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
-                null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
-                0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */);
-
-        // top cutout
-        mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP));
-
-        mScreenDecorations.start();
-
-        final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView)
-                mScreenDecorations.getOverlayView(R.id.display_cutout);
-        assertNotNull(cutoutView);
-        spyOn(cutoutView);
-        doReturn(mDisplay).when(cutoutView).getDisplay();
-
-        mScreenDecorations.mDisplayListener.onDisplayChanged(1);
-
-        verify(cutoutView, times(1)).onDisplayChanged(any());
-    }
-
-    @Test
     public void testHasSameProvidersWithNullOverlays() {
         setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
                 null /* roundedTopDrawable */, null /* roundedBottomDrawable */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index f933361..93a1868 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -24,12 +24,11 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.R as InternalR
 import com.android.systemui.R as SystemUIR
-import com.android.systemui.tests.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.tests.R
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
-
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
@@ -102,14 +101,11 @@
         assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
 
-        setupResources(radius = 100,
-                roundedTopDrawable = getTestsDrawable(R.drawable.rounded4px),
-                roundedBottomDrawable = getTestsDrawable(R.drawable.rounded5px))
-
+        roundedCornerResDelegate.physicalPixelDisplaySizeRatio = 2f
         roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
 
-        assertEquals(Size(4, 4), roundedCornerResDelegate.topRoundedSize)
-        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(8, 8), roundedCornerResDelegate.bottomRoundedSize)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index 5a50a9f..5dd1cfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.animation.TransitionLayout
@@ -78,6 +79,7 @@
     @Mock lateinit var mediaViewController: MediaViewController
     @Mock lateinit var smartspaceMediaData: SmartspaceMediaData
     @Captor lateinit var listener: ArgumentCaptor<MediaDataManager.Listener>
+    @Captor lateinit var visualStabilityCallback: ArgumentCaptor<OnReorderingAllowedListener>
 
     private val clock = FakeSystemClock()
     private lateinit var mediaCarouselController: MediaCarouselController
@@ -102,6 +104,8 @@
             debugLogger
         )
         verify(mediaDataManager).addListener(capture(listener))
+        verify(visualStabilityProvider)
+            .addPersistentReorderingAllowedListener(capture(visualStabilityCallback))
         whenever(mediaControlPanelFactory.get()).thenReturn(mediaPlayer)
         whenever(mediaPlayer.mediaViewController).thenReturn(mediaViewController)
         whenever(mediaDataManager.smartspaceMediaData).thenReturn(smartspaceMediaData)
@@ -374,4 +378,28 @@
         playerIndex = MediaPlayerData.getMediaPlayerIndex("playing local")
         assertEquals(playerIndex, 0)
     }
+
+    @Test
+    fun testRecommendationRemovedWhileNotVisible_updateHostVisibility() {
+        var result = false
+        mediaCarouselController.updateHostVisibility = { result = true }
+
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
+        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+
+        assertEquals(true, result)
+    }
+
+    @Test
+    fun testRecommendationRemovedWhileVisible_thenReorders_updateHostVisibility() {
+        var result = false
+        mediaCarouselController.updateHostVisibility = { result = true }
+
+        whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
+        listener.value.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY, false)
+        assertEquals(false, result)
+
+        visualStabilityCallback.value.onReorderingAllowed()
+        assertEquals(true, result)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index b86713d..451e911 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -39,6 +39,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.HotspotController;
 
@@ -122,4 +123,40 @@
                 .isEqualTo(mContext.getString(R.string.wifitrackerlib_admin_restricted_network));
         mockitoSession.finishMocking();
     }
+
+    @Test
+    public void testIcon_whenDisabled_isOffState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(false);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(false);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_off));
+    }
+
+    @Test
+    public void testIcon_whenTransient_isSearchState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(true);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_search));
+    }
+
+    @Test
+    public void testIcon_whenEnabled_isOnState() {
+        QSTile.BooleanState state = new QSTile.BooleanState();
+        when(mHotspotController.isHotspotTransient()).thenReturn(false);
+        when(mHotspotController.isHotspotEnabled()).thenReturn(true);
+
+        mTile.handleUpdateState(state, /* arg= */ null);
+
+        assertThat(state.icon)
+                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_hotspot_icon_on));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index ea70c26..0c070da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -21,9 +21,9 @@
 import android.content.res.Configuration
 import android.content.res.Resources
 import android.os.Handler
-import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
@@ -51,28 +51,17 @@
 @SmallTest
 class UiModeNightTileTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var mockContext: Context
-    @Mock
-    private lateinit var uiModeManager: UiModeManager
-    @Mock
-    private lateinit var resources: Resources
-    @Mock
-    private lateinit var qsLogger: QSLogger
-    @Mock
-    private lateinit var qsHost: QSTileHost
-    @Mock
-    private lateinit var metricsLogger: MetricsLogger
-    @Mock
-    private lateinit var statusBarStateController: StatusBarStateController
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var configurationController: ConfigurationController
-    @Mock
-    private lateinit var batteryController: BatteryController
-    @Mock
-    private lateinit var locationController: LocationController
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var uiModeManager: UiModeManager
+    @Mock private lateinit var resources: Resources
+    @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var qsHost: QSTileHost
+    @Mock private lateinit var metricsLogger: MetricsLogger
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var batteryController: BatteryController
+    @Mock private lateinit var locationController: LocationController
 
     private val uiEventLogger = UiEventLoggerFake()
     private val falsingManager = FalsingManagerFake()
@@ -85,7 +74,7 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
         configuration = Configuration()
-        mContext.addMockSystemService(Context.UI_MODE_SERVICE, uiModeManager)
+        mContext.addMockSystemService(UiModeManager::class.java, uiModeManager)
 
         `when`(qsHost.context).thenReturn(mockContext)
         `when`(qsHost.userContext).thenReturn(mContext)
@@ -93,7 +82,8 @@
         `when`(resources.configuration).thenReturn(configuration)
         `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
 
-        tile = UiModeNightTile(
+        tile =
+            UiModeNightTile(
                 qsHost,
                 testableLooper.looper,
                 Handler(testableLooper.looper),
@@ -104,7 +94,8 @@
                 qsLogger,
                 configurationController,
                 batteryController,
-                locationController)
+                locationController
+            )
     }
 
     @Test
@@ -115,7 +106,7 @@
         tile.handleUpdateState(state, /* arg= */ null)
 
         assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_on))
     }
 
     @Test
@@ -126,7 +117,7 @@
         tile.handleUpdateState(state, /* arg= */ null)
 
         assertThat(state.icon)
-                .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
+            .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_light_dark_theme_icon_off))
     }
 
     private fun setNightModeOn() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index d3c1dc9..a95a49c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -32,8 +34,10 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -57,6 +61,7 @@
     private Runnable mRoundnessCallback = mock(Runnable.class);
     private ExpandableNotificationRow mFirst;
     private ExpandableNotificationRow mSecond;
+    private NotificationRoundnessLogger mLogger = mock(NotificationRoundnessLogger.class);
     private float mSmallRadiusRatio;
 
     @Before
@@ -66,7 +71,9 @@
         mSmallRadiusRatio = resources.getDimension(R.dimen.notification_corner_radius_small)
                 / resources.getDimension(R.dimen.notification_corner_radius);
         mRoundnessManager = new NotificationRoundnessManager(
-                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
+                new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
+                mLogger,
+                mock(DumpManager.class));
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
@@ -337,6 +344,20 @@
         Assert.assertTrue(mSecond.isLastInSection());
     }
 
+    @Test
+    public void testLoggingOnRoundingUpdate() {
+        NotificationSection[] sections = new NotificationSection[]{
+                createSection(mFirst, mSecond),
+                createSection(null, null)
+        };
+        mRoundnessManager.updateRoundedChildren(sections);
+        verify(mLogger).onSectionCornersUpdated(sections, /*anyChanged=*/ true);
+        verify(mLogger, atLeast(1)).onCornersUpdated(eq(mFirst), anyBoolean(),
+                anyBoolean(), anyBoolean(), anyBoolean());
+        verify(mLogger, atLeast(1)).onCornersUpdated(eq(mSecond), anyBoolean(),
+                anyBoolean(), anyBoolean(), anyBoolean());
+    }
+
     private NotificationSection createSection(ExpandableNotificationRow first,
             ExpandableNotificationRow last) {
         NotificationSection section = mock(NotificationSection.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fda80a2..43d0fe9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 
@@ -80,6 +81,7 @@
                 mPowerManager,
                 mBroadcastDispatcher,
                 mDemoModeController,
+                mock(DumpManager.class),
                 new Handler(),
                 new Handler());
         // Can throw if updateEstimate is called on the main thread
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5c84a62..ae65dcb 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -1,6 +1,9 @@
 {
     "presubmit": [
         {
+            "name": "CtsLocationFineTestCases"
+        },
+        {
             "name": "CtsLocationCoarseTestCases"
         },
         {
@@ -66,10 +69,5 @@
             ],
             "file_patterns": ["ClipboardService\\.java"]
         }
-    ],
-    "postsubmit": [
-        {
-            "name": "CtsLocationFineTestCases"
-        }
     ]
 }
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f26d9f9..76cac93 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -24,6 +24,7 @@
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.isValidSubscriptionId;
 
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
 import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -167,6 +168,10 @@
     static final String VCN_CONFIG_FILE =
             new File(Environment.getDataSystemDirectory(), "vcn/configs.xml").getPath();
 
+    // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
+
     /* Binder context for this service */
     @NonNull private final Context mContext;
     @NonNull private final Dependencies mDeps;
@@ -360,15 +365,12 @@
 
     /** Notifies the VcnManagementService that external dependencies can be set up. */
     public void systemReady() {
-        // Always run on the handler thread to ensure consistency.
-        mHandler.post(() -> {
-            mNetworkProvider.register();
-            mContext.getSystemService(ConnectivityManager.class)
-                    .registerNetworkCallback(
-                            new NetworkRequest.Builder().clearCapabilities().build(),
-                            mTrackingNetworkCallback);
-            mTelephonySubscriptionTracker.register();
-        });
+        mNetworkProvider.register();
+        mContext.getSystemService(ConnectivityManager.class)
+                .registerNetworkCallback(
+                        new NetworkRequest.Builder().clearCapabilities().build(),
+                        mTrackingNetworkCallback);
+        mTelephonySubscriptionTracker.register();
     }
 
     private void enforcePrimaryUser() {
@@ -509,15 +511,22 @@
                         if (!mVcns.containsKey(subGrp)) {
                             startVcnLocked(subGrp, entry.getValue());
                         }
+
+                        // Cancel any scheduled teardowns for active subscriptions
+                        mHandler.removeCallbacksAndMessages(mVcns.get(subGrp));
                     }
                 }
 
-                // Schedule teardown of any VCN instances that have lost carrier privileges
+                // Schedule teardown of any VCN instances that have lost carrier privileges (after a
+                // delay)
                 for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) {
                     final ParcelUuid subGrp = entry.getKey();
                     final VcnConfig config = mConfigs.get(subGrp);
 
                     final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot);
+                    final boolean isValidActiveDataSubIdNotInVcnSubGrp =
+                            isValidSubscriptionId(snapshot.getActiveDataSubscriptionId())
+                                    && !isActiveSubGroup(subGrp, snapshot);
 
                     // TODO(b/193687515): Support multiple VCNs active at the same time
                     if (config == null
@@ -527,12 +536,31 @@
                         final ParcelUuid uuidToTeardown = subGrp;
                         final Vcn instanceToTeardown = entry.getValue();
 
-                        stopVcnLocked(uuidToTeardown);
+                        // TODO(b/193687515): Support multiple VCNs active at the same time
+                        // If directly switching to a subscription not in the current group,
+                        // teardown immediately to prevent other subscription's network from being
+                        // outscored by the VCN. Otherwise, teardown after a delay to ensure that
+                        // SIM profile switches do not trigger the VCN to cycle.
+                        final long teardownDelayMs =
+                                isValidActiveDataSubIdNotInVcnSubGrp
+                                        ? 0
+                                        : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS;
+                        mHandler.postDelayed(() -> {
+                            synchronized (mLock) {
+                                // Guard against case where this is run after a old instance was
+                                // torn down, and a new instance was started. Verify to ensure
+                                // correct instance is torn down. This could happen as a result of a
+                                // Carrier App manually removing/adding a VcnConfig.
+                                if (mVcns.get(uuidToTeardown) == instanceToTeardown) {
+                                    stopVcnLocked(uuidToTeardown);
 
-                        // TODO(b/181789060): invoke asynchronously after Vcn notifies
-                        // through VcnCallback
-                        notifyAllPermissionedStatusCallbacksLocked(
-                                uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+                                    // TODO(b/181789060): invoke asynchronously after Vcn notifies
+                                    // through VcnCallback
+                                    notifyAllPermissionedStatusCallbacksLocked(
+                                            uuidToTeardown, VCN_STATUS_CODE_INACTIVE);
+                                }
+                            }
+                        }, instanceToTeardown, teardownDelayMs);
                     } else {
                         // If this VCN's status has not changed, update it with the new snapshot
                         entry.getValue().updateSubscriptionSnapshot(mLastSnapshot);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d84b0e8..86918aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -668,10 +668,17 @@
     // we still create this new offload queue, but never ever put anything on it.
     final boolean mEnableOffloadQueue;
 
-    final BroadcastQueue mFgBroadcastQueue;
-    final BroadcastQueue mBgBroadcastQueue;
-    final BroadcastQueue mBgOffloadBroadcastQueue;
-    final BroadcastQueue mFgOffloadBroadcastQueue;
+    /**
+     * Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
+     * of the default {@link BroadcastQueueImpl}.
+     */
+    final boolean mEnableModernQueue;
+
+    static final int BROADCAST_QUEUE_FG = 0;
+    static final int BROADCAST_QUEUE_BG = 1;
+    static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
+    static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
+
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
     final BroadcastQueue[] mBroadcastQueues;
@@ -693,12 +700,16 @@
     }
 
     BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
+        if (mEnableModernQueue) {
+            return mBroadcastQueues[0];
+        }
+
         if (isOnFgOffloadQueue(flags)) {
             if (DEBUG_BROADCAST_BACKGROUND) {
                 Slog.i(TAG_BROADCAST,
                         "Broadcast intent " + cookie + " on foreground offload queue");
             }
-            return mFgOffloadBroadcastQueue;
+            return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
         }
 
         if (isOnBgOffloadQueue(flags)) {
@@ -706,14 +717,15 @@
                 Slog.i(TAG_BROADCAST,
                         "Broadcast intent " + cookie + " on background offload queue");
             }
-            return mBgOffloadBroadcastQueue;
+            return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
         }
 
         final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
         if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
                 "Broadcast intent " + cookie + " on "
                 + (isFg ? "foreground" : "background") + " queue");
-        return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
+        return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
+                : mBroadcastQueues[BROADCAST_QUEUE_BG];
     }
 
     private volatile int mDeviceOwnerUid = INVALID_UID;
@@ -2366,9 +2378,8 @@
         mPendingStartActivityUids = new PendingStartActivityUids();
         mUseFifoUiScheduling = false;
         mEnableOffloadQueue = false;
+        mEnableModernQueue = false;
         mBroadcastQueues = new BroadcastQueue[0];
-        mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue =
-                mFgOffloadBroadcastQueue = null;
         mComponentAliasResolver = new ComponentAliasResolver(this);
     }
 
@@ -2424,20 +2435,23 @@
 
         mEnableOffloadQueue = SystemProperties.getBoolean(
                 "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
+        mEnableModernQueue = SystemProperties.getBoolean(
+                "persist.device_config.activity_manager_native_boot.modern_queue_enabled", false);
 
-        mBroadcastQueues = new BroadcastQueue[4];
-        mFgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
-                "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
-        mBgBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
-                "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
-        mBgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
-                "offload_bg", offloadConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
-        mFgOffloadBroadcastQueue = new BroadcastQueueImpl(this, mHandler,
-                "offload_fg", foreConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
-        mBroadcastQueues[0] = mFgBroadcastQueue;
-        mBroadcastQueues[1] = mBgBroadcastQueue;
-        mBroadcastQueues[2] = mBgOffloadBroadcastQueue;
-        mBroadcastQueues[3] = mFgOffloadBroadcastQueue;
+        if (mEnableModernQueue) {
+            mBroadcastQueues = new BroadcastQueue[1];
+            mBroadcastQueues[0] = new BroadcastQueueModernImpl(this, mHandler, foreConstants);
+        } else {
+            mBroadcastQueues = new BroadcastQueue[4];
+            mBroadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(this, mHandler,
+                    "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
+            mBroadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(this, mHandler,
+                    "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+            mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(this, mHandler,
+                    "offload_bg", offloadConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+            mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(this, mHandler,
+                    "offload_fg", foreConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
+        }
 
         mServices = new ActiveServices(this);
         mCpHelper = new ContentProviderHelper(this, true);
@@ -8913,6 +8927,7 @@
      * @param incrementalMetrics metrics for apps installed on Incremental.
      * @param errorId a unique id to append to the dropbox headers.
      */
+    @SuppressWarnings("DoNotCall") // Ignore warning for synchronous to call to worker.run()
     public void addErrorToDropBox(String eventType,
             ProcessRecord process, String processName, String activityShortComponentName,
             String parentShortComponentName, ProcessRecord parentProcess,
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 3299ee3..ceff67e 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -44,6 +44,7 @@
 import static com.android.server.am.ActivityManagerService.getKsmInfo;
 import static com.android.server.am.ActivityManagerService.stringifyKBSize;
 import static com.android.server.am.LowMemDetector.ADJ_MEM_FACTOR_NOTHING;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_NONE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerService.DUMP_ACTIVITIES_CMD;
 
@@ -1047,17 +1048,7 @@
                 }
                 trimMemoryUiHiddenIfNecessaryLSP(app);
                 if (curProcState >= ActivityManager.PROCESS_STATE_HOME && !app.isKilledByAm()) {
-                    if (trimMemoryLevel < curLevel[0] && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ,
-                                        "Trimming memory of " + app.processName
-                                        + " to " + curLevel[0]);
-                            }
-                            thread.scheduleTrimMemory(curLevel[0]);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, curLevel[0], "Trimming memory of ");
                     profile.setTrimMemoryLevel(curLevel[0]);
                     step[0]++;
                     if (step[0] >= actualFactor) {
@@ -1073,31 +1064,11 @@
                     }
                 } else if (curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
                         && !app.isKilledByAm()) {
-                    if (trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
-                            && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ,
-                                        "Trimming memory of heavy-weight " + app.processName
-                                        + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
-                            }
-                            thread.scheduleTrimMemory(
-                                    ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,
+                            "Trimming memory of heavy-weight ");
                     profile.setTrimMemoryLevel(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
                 } else {
-                    if (trimMemoryLevel < fgTrimLevel && (thread = app.getThread()) != null) {
-                        try {
-                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                                Slog.v(TAG_OOM_ADJ, "Trimming memory of fg " + app.processName
-                                        + " to " + fgTrimLevel);
-                            }
-                            thread.scheduleTrimMemory(fgTrimLevel);
-                        } catch (RemoteException e) {
-                        }
-                    }
+                    scheduleTrimMemoryLSP(app, fgTrimLevel, "Trimming memory of fg ");
                     profile.setTrimMemoryLevel(fgTrimLevel);
                 }
             });
@@ -1128,22 +1099,28 @@
             // If this application is now in the background and it
             // had done UI, then give it the special trim level to
             // have it free UI resources.
-            final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
-            IApplicationThread thread;
-            if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
-                try {
-                    if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
-                        Slog.v(TAG_OOM_ADJ, "Trimming memory of bg-ui "
-                                + app.processName + " to " + level);
-                    }
-                    thread.scheduleTrimMemory(level);
-                } catch (RemoteException e) {
-                }
-            }
+            scheduleTrimMemoryLSP(app, ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN,
+                    "Trimming memory of bg-ui ");
             app.mProfile.setPendingUiClean(false);
         }
     }
 
+    @GuardedBy({"mService", "mProcLock"})
+    private void scheduleTrimMemoryLSP(ProcessRecord app, int level, String msg) {
+        IApplicationThread thread;
+        if (app.mProfile.getTrimMemoryLevel() < level && (thread = app.getThread()) != null) {
+            try {
+                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) {
+                    Slog.v(TAG_OOM_ADJ, msg + app.processName + " to " + level);
+                }
+                mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
+                        OOM_ADJ_REASON_NONE);
+                thread.scheduleTrimMemory(level);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     @GuardedBy("mProcLock")
     long getLowRamTimeSinceIdleLPr(long now) {
         return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now - mLowRamStartTime) : 0);
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
new file mode 100644
index 0000000..c18c65e
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -0,0 +1,298 @@
+/*
+ * 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 com.android.server.am.BroadcastQueue.checkState;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayDeque;
+
+/**
+ * Queue of pending {@link BroadcastRecord} entries intended for delivery to a
+ * specific process.
+ * <p>
+ * Each queue has a concept of being "runnable at" a particular time in the
+ * future, which supports arbitrarily pausing or delaying delivery on a
+ * per-process basis.
+ * <p>
+ * Internally each queue consists of a pending broadcasts which are waiting to
+ * be dispatched, and a single active broadcast which is currently being
+ * dispatched.
+ */
+class BroadcastProcessQueue implements Comparable<BroadcastProcessQueue> {
+    /**
+     * Default delay to apply to background broadcasts, giving a chance for
+     * debouncing of rapidly changing events.
+     */
+    // TODO: shift hard-coded defaults to BroadcastConstants
+    private static final long DELAY_DEFAULT_MILLIS = 10_000;
+
+    /**
+     * Default delay to apply to broadcasts targeting cached applications.
+     */
+    // TODO: shift hard-coded defaults to BroadcastConstants
+    private static final long DELAY_CACHED_MILLIS = 30_000;
+
+    final @NonNull String processName;
+    final int uid;
+
+    /**
+     * Linked list connection to another process under this {@link #uid} which
+     * has a different {@link #processName}.
+     */
+    @Nullable BroadcastProcessQueue next;
+
+    /**
+     * Currently known details about the target process; typically undefined
+     * when the process isn't actively running.
+     */
+    @Nullable ProcessRecord app;
+
+    /**
+     * Ordered collection of broadcasts that are waiting to be dispatched to
+     * this process, as a pair of {@link BroadcastRecord} and the index into
+     * {@link BroadcastRecord#receivers} that represents the receiver.
+     */
+    private final ArrayDeque<SomeArgs> mPending = new ArrayDeque<>();
+
+    /**
+     * Broadcast actively being dispatched to this process.
+     */
+    private @Nullable BroadcastRecord mActive;
+
+    /**
+     * Receiver actively being dispatched to in this process. This is an index
+     * into the {@link BroadcastRecord#receivers} list of {@link #mActive}.
+     */
+    private int mActiveIndex;
+
+    private int mCountForeground;
+    private int mCountOrdered;
+    private int mCountAlarm;
+
+    private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
+    private boolean mRunnableAtInvalidated;
+
+    private boolean mProcessCached;
+
+    public BroadcastProcessQueue(@NonNull String processName, int uid) {
+        this.processName = processName;
+        this.uid = uid;
+    }
+
+    /**
+     * Enqueue the given broadcast to be dispatched to this process at some
+     * future point in time. The target receiver is indicated by the given index
+     * into {@link BroadcastRecord#receivers}.
+     */
+    public void enqueueBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
+        // Detect situations where the incoming broadcast should cause us to
+        // recalculate when we'll be runnable
+        if (mPending.isEmpty()) {
+            invalidateRunnableAt();
+        }
+        if (record.isForeground()) {
+            mCountForeground++;
+            invalidateRunnableAt();
+        }
+        if (record.ordered) {
+            mCountOrdered++;
+            invalidateRunnableAt();
+        }
+        if (record.alarm) {
+            mCountAlarm++;
+            invalidateRunnableAt();
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = record;
+        args.argi1 = recordIndex;
+        mPending.addLast(args);
+    }
+
+    /**
+     * Update if this process is in the "cached" state, typically signaling that
+     * broadcast dispatch should be paused or delayed.
+     */
+    public void setProcessCached(boolean cached) {
+        if (mProcessCached != cached) {
+            mProcessCached = cached;
+            invalidateRunnableAt();
+        }
+    }
+
+    /**
+     * Return if we know of an actively running "warm" process for this queue.
+     */
+    public boolean isProcessWarm() {
+        return (app != null) && (app.getThread() != null) && !app.isKilled();
+    }
+
+    public int getPreferredSchedulingGroupLocked() {
+        if (mCountForeground > 0 || mCountOrdered > 0 || mCountAlarm > 0) {
+            // We have an important broadcast somewhere down the queue, so
+            // boost priority until we drain them all
+            return ProcessList.SCHED_GROUP_DEFAULT;
+        } else if ((mActive != null)
+                && (mActive.isForeground() || mActive.ordered || mActive.alarm)) {
+            // We have an important broadcast right now, so boost priority
+            return ProcessList.SCHED_GROUP_DEFAULT;
+        } else {
+            return ProcessList.SCHED_GROUP_BACKGROUND;
+        }
+    }
+
+    /**
+     * Set the currently active broadcast to the next pending broadcast.
+     */
+    public void makeActiveNextPending() {
+        // TODO: what if the next broadcast isn't runnable yet?
+        checkState(isRunnable(), "isRunnable");
+        final SomeArgs next = mPending.removeFirst();
+        mActive = (BroadcastRecord) next.arg1;
+        mActiveIndex = next.argi1;
+        next.recycle();
+        if (mActive.isForeground()) {
+            mCountForeground--;
+        }
+        if (mActive.ordered) {
+            mCountOrdered--;
+        }
+        if (mActive.alarm) {
+            mCountAlarm--;
+        }
+        invalidateRunnableAt();
+    }
+
+    /**
+     * Set the currently running broadcast to be idle.
+     */
+    public void makeActiveIdle() {
+        mActive = null;
+        mActiveIndex = 0;
+    }
+
+    public void setActiveDeliveryState(int deliveryState) {
+        checkState(isActive(), "isActive");
+        mActive.setDeliveryState(mActiveIndex, deliveryState);
+    }
+
+    public @NonNull BroadcastRecord getActive() {
+        checkState(isActive(), "isActive");
+        return mActive;
+    }
+
+    public @NonNull Object getActiveReceiver() {
+        checkState(isActive(), "isActive");
+        return mActive.receivers.get(mActiveIndex);
+    }
+
+    public boolean isActive() {
+        return mActive != null;
+    }
+
+    public boolean isRunnable() {
+        if (mRunnableAtInvalidated) updateRunnableAt();
+        return mRunnableAt != Long.MAX_VALUE;
+    }
+
+    /**
+     * Return time at which this process is considered runnable. This is
+     * typically the time at which the next pending broadcast was first
+     * enqueued, but it also reflects any pauses or delays that should be
+     * applied to the process.
+     * <p>
+     * Returns {@link Long#MAX_VALUE} when this queue isn't currently runnable,
+     * typically when the queue is empty or when paused.
+     */
+    public @UptimeMillisLong long getRunnableAt() {
+        if (mRunnableAtInvalidated) updateRunnableAt();
+        return mRunnableAt;
+    }
+
+    private void invalidateRunnableAt() {
+        mRunnableAtInvalidated = true;
+    }
+
+    /**
+     * Update {@link #getRunnableAt()} if it's currently invalidated.
+     */
+    private void updateRunnableAt() {
+        final SomeArgs next = mPending.peekFirst();
+        if (next != null) {
+            final long runnableAt = ((BroadcastRecord) next.arg1).enqueueTime;
+            if (mCountForeground > 0) {
+                mRunnableAt = runnableAt;
+            } else if (mCountOrdered > 0) {
+                mRunnableAt = runnableAt;
+            } else if (mCountAlarm > 0) {
+                mRunnableAt = runnableAt;
+            } else if (mProcessCached) {
+                mRunnableAt = runnableAt + DELAY_CACHED_MILLIS;
+            } else {
+                mRunnableAt = runnableAt + DELAY_DEFAULT_MILLIS;
+            }
+        } else {
+            mRunnableAt = Long.MAX_VALUE;
+        }
+    }
+
+    @Override
+    public int compareTo(BroadcastProcessQueue o) {
+        if (mRunnableAtInvalidated) updateRunnableAt();
+        if (o.mRunnableAtInvalidated) o.updateRunnableAt();
+        return Long.compare(mRunnableAt, o.mRunnableAt);
+    }
+
+    @Override
+    public String toString() {
+        return "BroadcastProcessQueue{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+    }
+
+    public String toShortString() {
+        return processName + "/" + UserHandle.formatUid(uid);
+    }
+
+    public void dumpLocked(@NonNull IndentingPrintWriter pw) {
+        if ((mActive == null) && mPending.isEmpty()) return;
+
+        pw.println(toShortString());
+        pw.increaseIndent();
+        if (mActive != null) {
+            pw.print("🏃 ");
+            pw.print(mActive.toShortString());
+            pw.print(' ');
+            pw.println(mActive.receivers.get(mActiveIndex));
+        }
+        for (SomeArgs args : mPending) {
+            final BroadcastRecord r = (BroadcastRecord) args.arg1;
+            pw.print("\u3000 ");
+            pw.print(r.toShortString());
+            pw.print(' ');
+            pw.println(r.receivers.get(args.argi1));
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 6814509..e6c446a 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -59,6 +60,16 @@
         mConstants.startObserving(mHandler, resolver);
     }
 
+    static void checkState(boolean state, String msg) {
+        if (!state) {
+            Slog.wtf(TAG, msg, new Throwable());
+        }
+    }
+
+    static void logv(String msg) {
+        Slog.v(TAG, msg);
+    }
+
     @Override
     public String toString() {
         return mQueueName;
@@ -74,6 +85,7 @@
      *         otherwise {@link ProcessList#SCHED_GROUP_UNDEFINED} if this queue
      *         has no opinion.
      */
+    @GuardedBy("mService")
     public abstract int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app);
 
     /**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 8c55b14..169f857 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1408,7 +1408,7 @@
                 info.activityInfo.applicationInfo, true,
                 r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                 new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
-                        r.intent.getAction(), getHostingRecordTriggerType(r)),
+                        r.intent.getAction(), r.getHostingRecordTriggerType()),
                 isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
                 (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
         if (r.curApp == null) {
@@ -1431,17 +1431,6 @@
         mPendingBroadcastRecvIndex = recIdx;
     }
 
-    private String getHostingRecordTriggerType(BroadcastRecord r) {
-        if (r.alarm) {
-            return HostingRecord.TRIGGER_TYPE_ALARM;
-        } else if (r.pushMessage) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
-        } else if (r.pushMessageOverQuota) {
-            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
-        }
-        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
-    }
-
     @Nullable
     private String getTargetPackage(BroadcastRecord r) {
         if (r.intent == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
new file mode 100644
index 0000000..b5e7b86
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -0,0 +1,649 @@
+/*
+ * 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.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
+import static com.android.server.am.BroadcastRecord.getReceiverUid;
+import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
+import android.app.UidObserver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Alternative {@link BroadcastQueue} implementation which pivots broadcasts to
+ * be dispatched on a per-process basis.
+ * <p>
+ * Each process now has its own broadcast queue represented by a
+ * {@link BroadcastProcessQueue} instance. Each queue has a concept of being
+ * "runnable at" a particular time in the future, which supports arbitrarily
+ * pausing or delaying delivery on a per-process basis.
+ */
+class BroadcastQueueModernImpl extends BroadcastQueue {
+    BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
+            BroadcastConstants constants) {
+        this(service, handler, constants, new BroadcastSkipPolicy(service),
+                new BroadcastHistory());
+    }
+
+    BroadcastQueueModernImpl(ActivityManagerService service, Handler handler,
+            BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
+            BroadcastHistory history) {
+        super(service, handler, "modern", constants, skipPolicy, history);
+        mLocalHandler = new Handler(handler.getLooper(), mLocalCallback);
+    }
+
+    // TODO: add support for ordered broadcasts
+    // TODO: add support for replacing pending broadcasts
+    // TODO: add support for merging pending broadcasts
+
+    // TODO: add trace points for debugging broadcast flows
+    // TODO: record broadcast state change timing statistics
+    // TODO: record historical broadcast statistics
+
+    // TODO: pause queues for apps involved in backup/restore
+    // TODO: pause queues when background services are running
+    // TODO: pause queues when processes are frozen
+
+    // TODO: clean up queues for removed apps
+
+    /**
+     * Maximum number of process queues to dispatch broadcasts to
+     * simultaneously.
+     */
+    // TODO: shift hard-coded defaults to BroadcastConstants
+    private static final int MAX_RUNNING_PROCESS_QUEUES = 4;
+
+    /**
+     * Map from UID to per-process broadcast queues. If a UID hosts more than
+     * one process, each additional process is stored as a linked list using
+     * {@link BroadcastProcessQueue#next}.
+     *
+     * @see #getProcessQueue
+     * @see #getOrCreateProcessQueue
+     */
+    @GuardedBy("mService")
+    private final SparseArray<BroadcastProcessQueue> mProcessQueues = new SparseArray<>();
+
+    /**
+     * Collection of queues which are "runnable". They're sorted by
+     * {@link BroadcastProcessQueue#getRunnableAt()} so that we prefer
+     * dispatching of longer-waiting broadcasts first.
+     */
+    @GuardedBy("mService")
+    private final ArrayList<BroadcastProcessQueue> mRunnable = new ArrayList<>();
+
+    /**
+     * Collection of queues which are "running". This will never be larger than
+     * {@link #MAX_RUNNING_PROCESS_QUEUES}.
+     */
+    @GuardedBy("mService")
+    private final ArrayList<BroadcastProcessQueue> mRunning = new ArrayList<>();
+
+    /**
+     * Single queue which is "running" but is awaiting a cold start to be
+     * completed via {@link #onApplicationAttachedLocked}. To optimize for
+     * system health we only request one cold start at a time.
+     */
+    @GuardedBy("mService")
+    private @Nullable BroadcastProcessQueue mRunningColdStart;
+
+    /**
+     * Collection of latches waiting for queue to go idle.
+     */
+    @GuardedBy("mService")
+    private final ArrayList<CountDownLatch> mWaitingForIdle = new ArrayList<>();
+
+    private static final int MSG_UPDATE_RUNNING_LIST = 1;
+
+    private void enqueueUpdateRunningList() {
+        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
+        mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
+    }
+
+    private final Handler mLocalHandler;
+
+    private final Handler.Callback mLocalCallback = (msg) -> {
+        switch (msg.what) {
+            case MSG_UPDATE_RUNNING_LIST: {
+                synchronized (mService) {
+                    updateRunningList();
+                }
+                return true;
+            }
+        }
+        return false;
+    };
+
+    /**
+     * Consider updating the list of "runnable" queues, specifically with
+     * relation to the given queue.
+     * <p>
+     * Typically called when {@link BroadcastProcessQueue#getRunnableAt()} might
+     * have changed, since that influences the order in which we'll promote a
+     * "runnable" queue to be "running."
+     */
+    @GuardedBy("mService")
+    private void updateRunnableList(@NonNull BroadcastProcessQueue queue) {
+        if (mRunning.contains(queue)) {
+            // Already running; they'll be reinserted into the runnable list
+            // once they finish running, so no need to update them now
+            return;
+        }
+
+        // TODO: better optimize by using insertion sort data structure
+        mRunnable.remove(queue);
+        if (queue.isRunnable()) {
+            mRunnable.add(queue);
+        }
+        mRunnable.sort(null);
+    }
+
+    /**
+     * Consider updating the list of "running" queues.
+     * <p>
+     * This method can promote "runnable" queues to become "running", subject to
+     * a maximum of {@link #MAX_RUNNING_PROCESS_QUEUES} warm processes and only
+     * one pending cold-start.
+     */
+    @GuardedBy("mService")
+    private void updateRunningList() {
+        int avail = MAX_RUNNING_PROCESS_QUEUES - mRunning.size();
+        if (avail == 0) return;
+
+        // If someone is waiting to go idle, everything is runnable now
+        final boolean waitingForIdle = !mWaitingForIdle.isEmpty();
+
+        // We're doing an update now, so remove any future update requests;
+        // we'll repost below if needed
+        mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
+
+        boolean updateOomAdj = false;
+        final long now = SystemClock.uptimeMillis();
+        for (int i = 0; i < mRunnable.size() && avail > 0; i++) {
+            final BroadcastProcessQueue queue = mRunnable.get(i);
+            final long runnableAt = queue.getRunnableAt();
+
+            // If queues beyond this point aren't ready to run yet, schedule
+            // another pass when they'll be runnable
+            if (runnableAt > now && !waitingForIdle) {
+                mLocalHandler.sendEmptyMessageAtTime(MSG_UPDATE_RUNNING_LIST, runnableAt);
+                break;
+            }
+
+            // We might not have heard about a newly running process yet, so
+            // consider refreshing if we think we're cold
+            updateWarmProcess(queue);
+
+            final boolean processWarm = queue.isProcessWarm();
+            if (!processWarm) {
+                // We only offer to run one cold-start at a time to preserve
+                // system resources; below we either claim that single slot or
+                // skip to look for another warm process
+                if (mRunningColdStart == null) {
+                    mRunningColdStart = queue;
+                } else {
+                    continue;
+                }
+            }
+
+            if (DEBUG_BROADCAST) logv("Promoting " + queue
+                    + " from runnable to running; process is " + queue.app);
+
+            // Allocate this available permit and start running!
+            mRunnable.remove(i);
+            mRunning.add(queue);
+            avail--;
+            i--;
+
+            queue.makeActiveNextPending();
+
+            // If we're already warm, schedule it; otherwise we'll wait for the
+            // cold start to circle back around
+            if (processWarm) {
+                scheduleReceiverWarmLocked(queue);
+            } else {
+                scheduleReceiverColdLocked(queue);
+            }
+
+            mService.enqueueOomAdjTargetLocked(queue.app);
+            updateOomAdj = true;
+        }
+
+        if (updateOomAdj) {
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+        }
+
+        if (waitingForIdle && isIdleLocked()) {
+            mWaitingForIdle.forEach((latch) -> latch.countDown());
+            mWaitingForIdle.clear();
+        }
+    }
+
+    @Override
+    public boolean onApplicationAttachedLocked(@NonNull ProcessRecord app) {
+        boolean didSomething = false;
+        if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
+            // We've been waiting for this app to cold start, and it's ready
+            // now; dispatch its next broadcast and clear the slot
+            scheduleReceiverWarmLocked(mRunningColdStart);
+            mRunningColdStart = null;
+
+            // We might be willing to kick off another cold start
+            enqueueUpdateRunningList();
+            didSomething = true;
+        }
+        return didSomething;
+    }
+
+    @Override
+    public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
+        return onApplicationCleanupLocked(app);
+    }
+
+    @Override
+    public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
+        return onApplicationCleanupLocked(app);
+    }
+
+    @Override
+    public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
+        boolean didSomething = false;
+        if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
+            // We've been waiting for this app to cold start, and it had
+            // trouble; clear the slot and fail delivery below
+            mRunningColdStart = null;
+
+            // We might be willing to kick off another cold start
+            enqueueUpdateRunningList();
+            didSomething = true;
+        }
+
+        final BroadcastProcessQueue queue = getProcessQueue(app);
+        if (queue != null) {
+            queue.app = null;
+
+            // If queue was running a broadcast, fail it
+            if (queue.isActive()) {
+                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                didSomething = true;
+            }
+        }
+
+        return didSomething;
+    }
+
+    @Override
+    public int getPreferredSchedulingGroupLocked(@NonNull ProcessRecord app) {
+        final BroadcastProcessQueue queue = getProcessQueue(app);
+        if ((queue != null) && mRunning.contains(queue)) {
+            return queue.getPreferredSchedulingGroupLocked();
+        }
+        return ProcessList.SCHED_GROUP_UNDEFINED;
+    }
+
+    @Override
+    public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
+        // TODO: handle empty receivers to deliver result immediately
+        if (r.receivers == null) return;
+
+        r.enqueueTime = SystemClock.uptimeMillis();
+        r.enqueueRealTime = SystemClock.elapsedRealtime();
+        r.enqueueClockTime = System.currentTimeMillis();
+
+        for (int i = 0; i < r.receivers.size(); i++) {
+            final Object receiver = r.receivers.get(i);
+            final BroadcastProcessQueue queue = getOrCreateProcessQueue(
+                    getReceiverProcessName(receiver), getReceiverUid(receiver));
+            queue.enqueueBroadcast(r, i);
+            updateRunnableList(queue);
+            enqueueUpdateRunningList();
+        }
+    }
+
+    private void scheduleReceiverColdLocked(@NonNull BroadcastProcessQueue queue) {
+        checkState(queue.isActive(), "isActive");
+
+        final BroadcastRecord r = queue.getActive();
+        final Object receiver = queue.getActiveReceiver();
+
+        final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
+        final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
+
+        final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND;
+        final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST,
+                component, r.intent.getAction(), r.getHostingRecordTriggerType());
+        final boolean isActivityCapable = (r.options != null
+                && r.options.getTemporaryAppAllowlistDuration() > 0);
+        final int zygotePolicyFlags = isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE
+                : ZYGOTE_POLICY_FLAG_EMPTY;
+        final boolean allowWhileBooting = (r.intent.getFlags()
+                & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0;
+
+        if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue);
+        queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags,
+                hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
+        if (queue.app == null) {
+            mRunningColdStart = null;
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+        }
+    }
+
+    private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
+        checkState(queue.isActive(), "isActive");
+
+        final ProcessRecord app = queue.app;
+        final BroadcastRecord r = queue.getActive();
+        final Object receiver = queue.getActiveReceiver();
+
+        // TODO: schedule ANR timeout trigger event
+        // TODO: apply temp allowlist exemptions
+        // TODO: apply background activity launch exemptions
+
+        if (mSkipPolicy.shouldSkip(r, receiver)) {
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            return;
+        }
+
+        final Intent receiverIntent = r.getReceiverIntent(receiver);
+        if (receiverIntent == null) {
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            return;
+        }
+
+        if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
+        final IApplicationThread thread = app.getThread();
+        if (thread != null) {
+            try {
+                queue.setActiveDeliveryState(BroadcastRecord.DELIVERY_SCHEDULED);
+                if (receiver instanceof BroadcastFilter) {
+                    thread.scheduleRegisteredReceiver(
+                            ((BroadcastFilter) receiver).receiverList.receiver, receiverIntent,
+                            r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky,
+                            r.userId, app.mState.getReportedProcState());
+
+                    // TODO: consider making registered receivers of unordered
+                    // broadcasts report results to detect ANRs
+                    if (!r.ordered) {
+                        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+                    }
+                } else {
+                    thread.scheduleReceiver(receiverIntent, ((ResolveInfo) receiver).activityInfo,
+                            null, r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+                            app.mState.getReportedProcState());
+                }
+            } catch (RemoteException e) {
+                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                synchronized (app.mService) {
+                    app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
+                }
+            }
+        } else {
+            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+        }
+    }
+
+    @Override
+    public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
+            @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
+            boolean waitForServices) {
+        final BroadcastProcessQueue queue = getProcessQueue(app);
+        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+    }
+
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, int deliveryState) {
+        checkState(queue.isActive(), "isActive");
+
+        if (deliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
+            Slog.w(TAG, "Failed delivery of " + queue.getActive() + " to " + queue);
+        }
+
+        queue.setActiveDeliveryState(deliveryState);
+
+        // TODO: cancel any outstanding ANR timeout
+        // TODO: limit number of broadcasts in a row to avoid starvation
+        // TODO: if we're the last receiver of this broadcast, record to history
+
+        if (queue.isRunnable() && queue.isProcessWarm()) {
+            // We're on a roll; move onto the next broadcast for this process
+            queue.makeActiveNextPending();
+            scheduleReceiverWarmLocked(queue);
+            return true;
+        } else {
+            // We've drained running broadcasts; maybe move back to runnable
+            queue.makeActiveIdle();
+            mRunning.remove(queue);
+            // App is no longer running a broadcast, so update its OOM
+            // adjust during our next pass; no need for an immediate update
+            mService.enqueueOomAdjTargetLocked(queue.app);
+            updateRunnableList(queue);
+            enqueueUpdateRunningList();
+            return false;
+        }
+    }
+
+    @Override
+    public boolean cleanupDisabledPackageReceiversLocked(String packageName,
+            Set<String> filterByClasses, int userId, boolean doit) {
+        // TODO: implement
+        return false;
+    }
+
+    @Override
+    void start(@NonNull ContentResolver resolver) {
+        super.start(resolver);
+
+        mService.registerUidObserver(new UidObserver() {
+            @Override
+            public void onUidCachedChanged(int uid, boolean cached) {
+                synchronized (mService) {
+                    BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+                    while (leaf != null) {
+                        leaf.setProcessCached(cached);
+                        updateRunnableList(leaf);
+                        leaf = leaf.next;
+                    }
+                    enqueueUpdateRunningList();
+                }
+            }
+        }, ActivityManager.UID_OBSERVER_CACHED, 0, "android");
+    }
+
+    @Override
+    public boolean isIdleLocked() {
+        return mRunnable.isEmpty() && mRunning.isEmpty();
+    }
+
+    @Override
+    public void waitForIdle(@Nullable PrintWriter pw) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        synchronized (mService) {
+            mWaitingForIdle.add(latch);
+        }
+        enqueueUpdateRunningList();
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public void waitForBarrier(@Nullable PrintWriter pw) {
+        // TODO: implement
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String describeStateLocked() {
+        return mRunnable.size() + " runnable, " + mRunning.size() + " running";
+    }
+
+    @Override
+    public boolean isDelayBehindServices() {
+        // TODO: implement
+        return false;
+    }
+
+    @Override
+    public void backgroundServicesFinishedLocked(int userId) {
+        // TODO: implement
+    }
+
+    private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
+        if (!queue.isProcessWarm()) {
+            queue.app = mService.getProcessRecordLocked(queue.processName, queue.uid);
+        }
+    }
+
+    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull ProcessRecord app) {
+        return getOrCreateProcessQueue(app.processName, app.info.uid);
+    }
+
+    private @NonNull BroadcastProcessQueue getOrCreateProcessQueue(@NonNull String processName,
+            int uid) {
+        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+        while (leaf != null) {
+            if (Objects.equals(leaf.processName, processName)) {
+                return leaf;
+            } else if (leaf.next == null) {
+                break;
+            }
+            leaf = leaf.next;
+        }
+
+        BroadcastProcessQueue created = new BroadcastProcessQueue(processName, uid);
+        created.app = mService.getProcessRecordLocked(processName, uid);
+
+        if (leaf == null) {
+            mProcessQueues.put(uid, created);
+        } else {
+            leaf.next = created;
+        }
+        return created;
+    }
+
+    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull ProcessRecord app) {
+        return getProcessQueue(app.processName, app.info.uid);
+    }
+
+    private @Nullable BroadcastProcessQueue getProcessQueue(@NonNull String processName, int uid) {
+        BroadcastProcessQueue leaf = mProcessQueues.get(uid);
+        while (leaf != null) {
+            if (Objects.equals(leaf.processName, processName)) {
+                return leaf;
+            }
+            leaf = leaf.next;
+        }
+        return null;
+    }
+
+    @Override
+    public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
+        mHistory.dumpDebug(proto);
+        proto.end(token);
+    }
+
+    @Override
+    public boolean dumpLocked(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+            @NonNull String[] args, int opti, boolean dumpAll, @Nullable String dumpPackage,
+            boolean needSep) {
+        final long now = SystemClock.uptimeMillis();
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.increaseIndent();
+
+        ipw.println();
+        ipw.println("📋 Per-process queues:");
+        ipw.increaseIndent();
+        for (int i = 0; i < mProcessQueues.size(); i++) {
+            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+            while (leaf != null) {
+                leaf.dumpLocked(ipw);
+                leaf = leaf.next;
+            }
+        }
+        ipw.decreaseIndent();
+
+        ipw.println();
+        ipw.println("🧍 Runnable:");
+        ipw.increaseIndent();
+        if (mRunnable.isEmpty()) {
+            ipw.println("(none)");
+        } else {
+            for (BroadcastProcessQueue queue : mRunnable) {
+                TimeUtils.formatDuration(queue.getRunnableAt(), now, ipw);
+                ipw.print(' ');
+                ipw.println(queue.toShortString());
+            }
+        }
+        ipw.decreaseIndent();
+
+        ipw.println();
+        ipw.println("🏃 Running:");
+        ipw.increaseIndent();
+        if (mRunning.isEmpty()) {
+            ipw.println("(none)");
+        } else {
+            for (BroadcastProcessQueue queue : mRunning) {
+                if (queue == mRunningColdStart) {
+                    ipw.print("🥶 ");
+                } else {
+                    ipw.print("\u3000 ");
+                }
+                ipw.println(queue.toShortString());
+            }
+        }
+        ipw.decreaseIndent();
+
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+        needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
+        return needSep;
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 817831c..96fd362 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -84,6 +84,7 @@
     final BroadcastOptions options; // BroadcastOptions supplied by caller
     final List receivers;   // contains BroadcastFilter and ResolveInfo
     final int[] delivery;   // delivery state of each receiver
+    final long[] scheduledTime; // uptimeMillis when each receiver was scheduled
     final long[] duration;   // duration a receiver took to process broadcast
     IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -127,10 +128,18 @@
     static final int CALL_DONE_RECEIVE = 3;
     static final int WAITING_SERVICES = 4;
 
+    /** Initial state: waiting to run in future */
     static final int DELIVERY_PENDING = 0;
+    /** Terminal state: finished successfully */
     static final int DELIVERY_DELIVERED = 1;
+    /** Terminal state: skipped due to internal policy */
     static final int DELIVERY_SKIPPED = 2;
+    /** Terminal state: timed out during attempted delivery */
     static final int DELIVERY_TIMEOUT = 3;
+    /** Intermediate state: currently executing */
+    static final int DELIVERY_SCHEDULED = 4;
+    /** Terminal state: failure to dispatch */
+    static final int DELIVERY_FAILURE = 5;
 
     ProcessRecord curApp;       // hosting application of current receiver.
     ComponentName curComponent; // the receiver class that is currently running.
@@ -253,6 +262,8 @@
                 case DELIVERY_DELIVERED: pw.print("Deliver"); break;
                 case DELIVERY_SKIPPED:   pw.print("Skipped"); break;
                 case DELIVERY_TIMEOUT:   pw.print("Timeout"); break;
+                case DELIVERY_SCHEDULED: pw.print("Schedul"); break;
+                case DELIVERY_FAILURE:   pw.print("Failure"); break;
                 default:                 pw.print("???????"); break;
             }
             pw.print(" "); TimeUtils.formatDuration(duration[i], pw);
@@ -300,6 +311,7 @@
         options = _options;
         receivers = _receivers;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
+        scheduledTime = new long[delivery.length];
         duration = new long[delivery.length];
         resultTo = _resultTo;
         resultCode = _resultCode;
@@ -346,6 +358,7 @@
         options = from.options;
         receivers = from.receivers;
         delivery = from.delivery;
+        scheduledTime = from.scheduledTime;
         duration = from.duration;
         resultTo = from.resultTo;
         enqueueTime = from.enqueueTime;
@@ -497,7 +510,77 @@
         return ret;
     }
 
-    int getReceiverUid(Object receiver) {
+    /**
+     * Update the delivery state of the given {@link #receivers} index.
+     * Automatically updates any time measurements related to state changes.
+     */
+    void setDeliveryState(int index, int deliveryState) {
+        delivery[index] = deliveryState;
+
+        switch (deliveryState) {
+            case DELIVERY_DELIVERED:
+                duration[index] = SystemClock.uptimeMillis() - scheduledTime[index];
+                break;
+            case DELIVERY_SCHEDULED:
+                scheduledTime[index] = SystemClock.uptimeMillis();
+                break;
+        }
+    }
+
+    boolean isForeground() {
+        return (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
+    }
+
+    boolean isReplacePending() {
+        return (intent.getFlags() & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+    }
+
+    @NonNull String getHostingRecordTriggerType() {
+        if (alarm) {
+            return HostingRecord.TRIGGER_TYPE_ALARM;
+        } else if (pushMessage) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+        } else if (pushMessageOverQuota) {
+            return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+        }
+        return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+    }
+
+    /**
+     * Return an instance of {@link #intent} specialized for the given receiver.
+     * For example, this returns a new specialized instance if the extras need
+     * to be filtered, or a {@link ResolveInfo} needs to be configured.
+     *
+     * @return a specialized intent, otherwise {@code null} to indicate that the
+     *         broadcast should not be delivered to this receiver, typically due
+     *         to it being filtered away by {@link #filterExtrasForReceiver}.
+     */
+    @Nullable Intent getReceiverIntent(@NonNull Object receiver) {
+        Intent newIntent = null;
+        if (filterExtrasForReceiver != null) {
+            final Bundle extras = intent.getExtras();
+            if (extras != null) {
+                final int receiverUid = getReceiverUid(receiver);
+                final Bundle filteredExtras = filterExtrasForReceiver.apply(receiverUid, extras);
+                if (filteredExtras == null) {
+                    // Completely filtered; skip the broadcast!
+                    return null;
+                } else {
+                    newIntent = new Intent(intent);
+                    newIntent.replaceExtras(filteredExtras);
+                }
+            }
+        }
+        if (receiver instanceof ResolveInfo) {
+            if (newIntent == null) {
+                newIntent = new Intent(intent);
+            }
+            newIntent.setComponent(((ResolveInfo) receiver).activityInfo.getComponentName());
+        }
+        return (newIntent != null) ? newIntent : intent;
+    }
+
+    static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
             return ((BroadcastFilter) receiver).owningUid;
         } else /* if (receiver instanceof ResolveInfo) */ {
@@ -505,6 +588,14 @@
         }
     }
 
+    static String getReceiverProcessName(@NonNull Object receiver) {
+        if (receiver instanceof BroadcastFilter) {
+            return ((BroadcastFilter) receiver).receiverList.app.processName;
+        } else /* if (receiver instanceof ResolveInfo) */ {
+            return ((ResolveInfo) receiver).activityInfo.processName;
+        }
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
@@ -561,6 +652,10 @@
             + " u" + userId + " " + intent.getAction() + "}";
     }
 
+    public String toShortString() {
+        return intent.getAction() + "/u" + userId;
+    }
+
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         long token = proto.start(fieldId);
         proto.write(BroadcastRecordProto.USER_ID, userId);
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 9569848..e9b5030 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
 import static com.android.server.am.ActivityManagerService.checkComponentPermission;
 import static com.android.server.am.BroadcastQueue.TAG;
@@ -35,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
-import android.os.Bundle;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -59,6 +57,18 @@
 
     /**
      * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+     * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+     */
+    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
+        if (target instanceof BroadcastFilter) {
+            return shouldSkip(r, (BroadcastFilter) target);
+        } else {
+            return shouldSkip(r, (ResolveInfo) target);
+        }
+    }
+
+    /**
+     * Determine if the given {@link BroadcastRecord} is eligible to be sent to
      * the given {@link ResolveInfo}.
      */
     public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 6775c99..7c7b01c9 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1641,7 +1641,7 @@
                 }
                 mInjector.updateUserConfiguration();
                 updateCurrentProfileIds();
-                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+                mInjector.getWindowManager().setCurrentUser(userId);
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
                 // with the option to show the user switcher on the keyguard.
@@ -1655,7 +1655,6 @@
             } else {
                 final Integer currentUserIdInt = mCurrentUserId;
                 updateCurrentProfileIds();
-                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
                 synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 676dc19..6a53978 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3210,41 +3210,44 @@
                 return;
             }
 
-            if (mSession != null && mMobikeEnabled) {
-                Log.d(
-                        TAG,
-                        "IKE Session has mobility. Delay handleSessionLost for losing network "
-                                + network
-                                + " on session with token "
-                                + mCurrentToken);
+            Log.d(TAG, "Schedule a delay handleSessionLost for losing network "
+                            + network
+                            + " on session with token "
+                            + mCurrentToken);
 
-                final int token = mCurrentToken;
-                // Delay the teardown in case a new network will be available soon. For example,
-                // during handover between two WiFi networks, Android will disconnect from the
-                // first WiFi and then connects to the second WiFi.
-                mScheduledHandleNetworkLostFuture =
-                        mExecutor.schedule(
-                                () -> {
-                                    if (isActiveToken(token)) {
-                                        handleSessionLost(null /* exception */, network);
-                                    } else {
-                                        Log.d(
-                                                TAG,
-                                                "Scheduled handleSessionLost fired for "
-                                                        + "obsolete token "
-                                                        + token);
+            final int token = mCurrentToken;
+            // Delay the teardown in case a new network will be available soon. For example,
+            // during handover between two WiFi networks, Android will disconnect from the
+            // first WiFi and then connects to the second WiFi.
+            mScheduledHandleNetworkLostFuture =
+                    mExecutor.schedule(
+                            () -> {
+                                if (isActiveToken(token)) {
+                                    handleSessionLost(new IkeNetworkLostException(network),
+                                            network);
+
+                                    synchronized (Vpn.this) {
+                                        // Ignore stale runner.
+                                        if (mVpnRunner != this) return;
+
+                                        updateState(DetailedState.DISCONNECTED,
+                                                "Network lost");
                                     }
+                                } else {
+                                    Log.d(
+                                            TAG,
+                                            "Scheduled handleSessionLost fired for "
+                                                    + "obsolete token "
+                                                    + token);
+                                }
 
-                                    // Reset mScheduledHandleNetworkLostFuture since it's
-                                    // already run on executor thread.
-                                    mScheduledHandleNetworkLostFuture = null;
-                                },
-                                NETWORK_LOST_TIMEOUT_MS,
-                                TimeUnit.MILLISECONDS);
-            } else {
-                Log.d(TAG, "Call handleSessionLost for losing network " + network);
-                handleSessionLost(null /* exception */, network);
-            }
+                                // Reset mScheduledHandleNetworkLostFuture since it's
+                                // already run on executor thread.
+                                mScheduledHandleNetworkLostFuture = null;
+                            },
+                            NETWORK_LOST_TIMEOUT_MS,
+                            TimeUnit.MILLISECONDS);
+
         }
 
         private void cancelHandleNetworkLostTimeout() {
@@ -4185,8 +4188,6 @@
      */
     @NonNull
     public synchronized List<String> getAppExclusionList(@NonNull String packageName) {
-        enforceNotRestrictedUser();
-
         final long oldId = Binder.clearCallingIdentity();
         try {
             final byte[] bytes = getVpnProfileStore().get(getVpnAppExcludedForPackage(packageName));
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 8e00ccf..44c8e18 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -21,8 +21,11 @@
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
 import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
+import static com.android.server.devicestate.OverrideRequestController.STATUS_UNKNOWN;
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -106,6 +109,8 @@
     private final BinderService mBinderService;
     @NonNull
     private final OverrideRequestController mOverrideRequestController;
+    @NonNull
+    private final DeviceStateProviderListener mDeviceStateProviderListener;
     @VisibleForTesting
     @NonNull
     public ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -139,6 +144,12 @@
     @NonNull
     private Optional<OverrideRequest> mActiveOverride = Optional.empty();
 
+    // The current active base state override request. When set the device state specified here will
+    // replace the value in mBaseState.
+    @GuardedBy("mLock")
+    @NonNull
+    private Optional<OverrideRequest> mActiveBaseStateOverride = Optional.empty();
+
     // List of processes registered to receive notifications about changes to device state and
     // request status indexed by process id.
     @GuardedBy("mLock")
@@ -177,7 +188,8 @@
         mOverrideRequestController = new OverrideRequestController(
                 this::onOverrideRequestStatusChangedLocked);
         mDeviceStatePolicy = policy;
-        mDeviceStatePolicy.getDeviceStateProvider().setListener(new DeviceStateProviderListener());
+        mDeviceStateProviderListener = new DeviceStateProviderListener();
+        mDeviceStatePolicy.getDeviceStateProvider().setListener(mDeviceStateProviderListener);
         mBinderService = new BinderService();
         mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
     }
@@ -257,6 +269,21 @@
         }
     }
 
+    /**
+     * Returns the current override base state, or {@link Optional#empty()} if no override state is
+     * requested. If an override base state is present, the returned state will be the same as
+     * the base state returned from {@link #getBaseState()}.
+     */
+    @NonNull
+    Optional<DeviceState> getOverrideBaseState() {
+        synchronized (mLock) {
+            if (mActiveBaseStateOverride.isPresent()) {
+                return getStateLocked(mActiveBaseStateOverride.get().getRequestedState());
+            }
+            return Optional.empty();
+        }
+    }
+
     /** Returns the list of currently supported device states. */
     DeviceState[] getSupportedStates() {
         synchronized (mLock) {
@@ -366,6 +393,7 @@
             }
 
             final DeviceState baseState = baseStateOptional.get();
+
             if (mBaseState.isPresent() && mBaseState.get().equals(baseState)) {
                 // Base state hasn't changed. Nothing to do.
                 return;
@@ -375,7 +403,7 @@
             if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
                 mOverrideRequestController.cancelOverrideRequest();
             }
-            mOverrideRequestController.handleBaseStateChanged();
+            mOverrideRequestController.handleBaseStateChanged(identifier);
             updatePendingStateLocked();
 
             if (!mPendingState.isPresent()) {
@@ -528,16 +556,41 @@
         }
     }
 
+    @GuardedBy("mLock")
     private void onOverrideRequestStatusChangedLocked(@NonNull OverrideRequest request,
             @OverrideRequestController.RequestStatus int status) {
-        if (status == STATUS_ACTIVE) {
-            mActiveOverride = Optional.of(request);
-        } else if (status == STATUS_CANCELED) {
-            if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
-                mActiveOverride = Optional.empty();
+        if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+            switch (status) {
+                case STATUS_ACTIVE:
+                    enableBaseStateRequestLocked(request);
+                    return;
+                case STATUS_CANCELED:
+                    if (mActiveBaseStateOverride.isPresent()
+                            && mActiveBaseStateOverride.get() == request) {
+                        mActiveBaseStateOverride = Optional.empty();
+                    }
+                    break;
+                case STATUS_UNKNOWN: // same as default
+                default:
+                    throw new IllegalArgumentException("Unknown request status: " + status);
+            }
+        } else if (request.getRequestType() == OVERRIDE_REQUEST_TYPE_EMULATED_STATE) {
+            switch (status) {
+                case STATUS_ACTIVE:
+                    mActiveOverride = Optional.of(request);
+                    break;
+                case STATUS_CANCELED:
+                    if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
+                        mActiveOverride = Optional.empty();
+                    }
+                    break;
+                case STATUS_UNKNOWN: // same as default
+                default:
+                    throw new IllegalArgumentException("Unknown request status: " + status);
             }
         } else {
-            throw new IllegalArgumentException("Unknown request status: " + status);
+            throw new IllegalArgumentException(
+                        "Unknown OverrideRest type: " + request.getRequestType());
         }
 
         boolean updatedPendingState = updatePendingStateLocked();
@@ -564,6 +617,18 @@
         mHandler.post(this::notifyPolicyIfNeeded);
     }
 
+    /**
+     * Sets the new base state of the device and notifies the process that made the base state
+     * override request that the request is now active.
+     */
+    @GuardedBy("mLock")
+    private void enableBaseStateRequestLocked(OverrideRequest request) {
+        setBaseState(request.getRequestedState());
+        mActiveBaseStateOverride = Optional.of(request);
+        ProcessRecord processRecord = mProcessRecords.get(request.getPid());
+        processRecord.notifyRequestActiveAsync(request.getToken());
+    }
+
     private void registerProcess(int pid, IDeviceStateManagerCallback callback) {
         synchronized (mLock) {
             if (mProcessRecords.contains(pid)) {
@@ -606,7 +671,8 @@
                         + " has no registered callback.");
             }
 
-            if (mOverrideRequestController.hasRequest(token)) {
+            if (mOverrideRequestController.hasRequest(token,
+                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE)) {
                 throw new IllegalStateException("Request has already been made for the supplied"
                         + " token: " + token);
             }
@@ -617,7 +683,8 @@
                         + " is not supported.");
             }
 
-            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags);
+            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+                    OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
             mOverrideRequestController.addRequest(request);
         }
     }
@@ -633,6 +700,44 @@
         }
     }
 
+    private void requestBaseStateOverrideInternal(int state, int flags, int callingPid,
+            @NonNull IBinder token) {
+        synchronized (mLock) {
+            final Optional<DeviceState> deviceState = getStateLocked(state);
+            if (!deviceState.isPresent()) {
+                throw new IllegalArgumentException("Requested state: " + state
+                        + " is not supported.");
+            }
+
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+
+            if (mOverrideRequestController.hasRequest(token,
+                    OVERRIDE_REQUEST_TYPE_BASE_STATE)) {
+                throw new IllegalStateException("Request has already been made for the supplied"
+                        + " token: " + token);
+            }
+
+            OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
+                    OVERRIDE_REQUEST_TYPE_BASE_STATE);
+            mOverrideRequestController.addBaseStateRequest(request);
+        }
+    }
+
+    private void cancelBaseStateOverrideInternal(int callingPid) {
+        synchronized (mLock) {
+            final ProcessRecord processRecord = mProcessRecords.get(callingPid);
+            if (processRecord == null) {
+                throw new IllegalStateException("Process " + callingPid
+                        + " has no registered callback.");
+            }
+            setBaseState(mDeviceStateProviderListener.mCurrentBaseState);
+        }
+    }
+
     private void dumpInternal(PrintWriter pw) {
         pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
 
@@ -729,6 +834,8 @@
     }
 
     private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
+        @IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
+
         @Override
         public void onSupportedDeviceStatesChanged(DeviceState[] newDeviceStates) {
             if (newDeviceStates.length == 0) {
@@ -743,7 +850,7 @@
             if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
                 throw new IllegalArgumentException("Invalid identifier: " + identifier);
             }
-
+            mCurrentBaseState = identifier;
             setBaseState(identifier);
         }
     }
@@ -895,6 +1002,38 @@
         }
 
         @Override // Binder call
+        public void requestBaseStateOverride(IBinder token, int state, int flags) {
+            final int callingPid = Binder.getCallingPid();
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to control base state of device.");
+
+            if (token == null) {
+                throw new IllegalArgumentException("Request token must not be null.");
+            }
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                requestBaseStateOverrideInternal(state, flags, callingPid, token);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
+        public void cancelBaseStateOverride() {
+            final int callingPid = Binder.getCallingPid();
+            getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+                    "Permission required to control base state of device.");
+
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                cancelBaseStateOverrideInternal(callingPid);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override // Binder call
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver result) {
             new DeviceStateManagerShellCommand(DeviceStateManagerService.this)
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 659ee75..8c6068d 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -16,11 +16,8 @@
 
 package com.android.server.devicestate;
 
-import static android.Manifest.permission.CONTROL_DEVICE_STATE;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.Context;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
@@ -39,6 +36,8 @@
 public class DeviceStateManagerShellCommand extends ShellCommand {
     @Nullable
     private static DeviceStateRequest sLastRequest;
+    @Nullable
+    private static DeviceStateRequest sLastBaseStateRequest;
 
     private final DeviceStateManagerService mService;
     private final DeviceStateManager mClient;
@@ -58,6 +57,8 @@
         switch (cmd) {
             case "state":
                 return runState(pw);
+            case "base-state":
+                return runBaseState(pw);
             case "print-state":
                 return runPrintState(pw);
             case "print-states":
@@ -89,10 +90,6 @@
             return 0;
         }
 
-        final Context context = mService.getContext();
-        context.enforceCallingOrSelfPermission(
-                CONTROL_DEVICE_STATE,
-                "Permission required to request device state.");
         final long callingIdentity = Binder.clearCallingIdentity();
         try {
             if ("reset".equals(nextArg)) {
@@ -127,6 +124,47 @@
         return 0;
     }
 
+    private int runBaseState(PrintWriter pw) {
+        final String nextArg = getNextArg();
+        if (nextArg == null) {
+            printAllStates(pw);
+            return 0;
+        }
+
+        final long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            if ("reset".equals(nextArg)) {
+                if (sLastBaseStateRequest != null) {
+                    mClient.cancelBaseStateOverride();
+                    sLastBaseStateRequest = null;
+                }
+            } else {
+                int requestedState = Integer.parseInt(nextArg);
+                DeviceStateRequest request = DeviceStateRequest.newBuilder(requestedState).build();
+
+                mClient.requestBaseStateOverride(request, null, null);
+
+                sLastBaseStateRequest = request;
+            }
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: requested state should be an integer");
+            return -1;
+        } catch (IllegalArgumentException e) {
+            getErrPrintWriter().println("Error: " + e.getMessage());
+            getErrPrintWriter().println("-------------------");
+            getErrPrintWriter().println("Run:");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("    print-states");
+            getErrPrintWriter().println("");
+            getErrPrintWriter().println("to get the list of currently supported device states");
+            return -1;
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
+        return 0;
+    }
+
     private int runPrintState(PrintWriter pw) {
         Optional<DeviceState> deviceState = mService.getCommittedState();
         if (deviceState.isPresent()) {
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 35a4c84..325e384 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -16,9 +16,13 @@
 
 package com.android.server.devicestate;
 
+import android.annotation.IntDef;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A request to override the state managed by {@link DeviceStateManagerService}.
  *
@@ -30,13 +34,47 @@
     private final int mRequestedState;
     @DeviceStateRequest.RequestFlags
     private final int mFlags;
+    @OverrideRequestType
+    private final int mRequestType;
+
+    /**
+     * Denotes that the request is meant to override the emulated state of the device. This will
+     * not change the base (physical) state of the device.
+     *
+     * This request type should be used if you are looking to emulate a device state for a feature
+     * but want the system to be aware of the physical state of the device.
+     */
+    public static final int OVERRIDE_REQUEST_TYPE_EMULATED_STATE = 0;
+
+    /**
+     * Denotes that the request is meant to override the base (physical) state of the device.
+     * Overriding the base state may not change the emulated state of the device if there is also an
+     * override request active for that property.
+     *
+     * This request type should only be used for testing, where you want to simulate the physical
+     * state of the device changing.
+     */
+    public static final int OVERRIDE_REQUEST_TYPE_BASE_STATE = 1;
+
+    /**
+     * Flags for signifying the type of {@link OverrideRequest}.
+     *
+     * @hide
+     */
+    @IntDef(flag = true, prefix = { "REQUEST_TYPE_" }, value = {
+            OVERRIDE_REQUEST_TYPE_BASE_STATE,
+            OVERRIDE_REQUEST_TYPE_EMULATED_STATE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OverrideRequestType {}
 
     OverrideRequest(IBinder token, int pid, int requestedState,
-            @DeviceStateRequest.RequestFlags int flags) {
+            @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
         mRequestedState = requestedState;
         mFlags = flags;
+        mRequestType = requestType;
     }
 
     IBinder getToken() {
@@ -55,4 +93,9 @@
     int getFlags() {
         return mFlags;
     }
+
+    @OverrideRequestType
+    int getRequestType() {
+        return mRequestType;
+    }
 }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 01f5a09..e39c732 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -75,6 +75,8 @@
 
     // Handle to the current override request, null if none.
     private OverrideRequest mRequest;
+    // Handle to the current base state override request, null if none.
+    private OverrideRequest mBaseStateRequest;
 
     private boolean mStickyRequestsAllowed;
     // The current request has outlived their process.
@@ -111,13 +113,23 @@
         }
     }
 
+    void addBaseStateRequest(@NonNull OverrideRequest request) {
+        OverrideRequest previousRequest = mBaseStateRequest;
+        mBaseStateRequest = request;
+        mListener.onStatusChanged(request, STATUS_ACTIVE);
+
+        if (previousRequest != null) {
+            cancelRequestLocked(previousRequest);
+        }
+    }
+
     /**
      * Cancels the request with the specified {@code token} and notifies the listener of all changes
      * to request status as a result of this operation.
      */
     void cancelRequest(@NonNull OverrideRequest request) {
         // Either don't have a current request or attempting to cancel an already cancelled request
-        if (!hasRequest(request.getToken())) {
+        if (!hasRequest(request.getToken(), request.getRequestType())) {
             return;
         }
         cancelCurrentRequestLocked();
@@ -144,11 +156,24 @@
     }
 
     /**
+     * Cancels the current base state override request, this could be due to the physical
+     * configuration of the device changing.
+     */
+    void cancelBaseStateOverrideRequest() {
+        cancelCurrentBaseStateRequestLocked();
+    }
+
+    /**
      * Returns {@code true} if this controller is current managing a request with the specified
      * {@code token}, {@code false} otherwise.
      */
-    boolean hasRequest(@NonNull IBinder token) {
-        return mRequest != null && token == mRequest.getToken();
+    boolean hasRequest(@NonNull IBinder token,
+            @OverrideRequest.OverrideRequestType int requestType) {
+        if (requestType == OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE) {
+            return mBaseStateRequest != null && token == mBaseStateRequest.getToken();
+        } else {
+            return mRequest != null && token == mRequest.getToken();
+        }
     }
 
     /**
@@ -157,11 +182,11 @@
      * operation.
      */
     void handleProcessDied(int pid) {
-        if (mRequest == null) {
-            return;
+        if (mBaseStateRequest != null && mBaseStateRequest.getPid() == pid) {
+            cancelCurrentBaseStateRequestLocked();
         }
 
-        if (mRequest.getPid() == pid) {
+        if (mRequest != null && mRequest.getPid() == pid) {
             if (mStickyRequestsAllowed) {
                 // Do not cancel the requests now because sticky requests are allowed. These
                 // requests will be cancelled on a call to cancelStickyRequests().
@@ -176,7 +201,10 @@
      * Notifies the controller that the base state has changed. The controller will notify the
      * listener of all changes to request status as a result of this change.
      */
-    void handleBaseStateChanged() {
+    void handleBaseStateChanged(int state) {
+        if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) {
+            cancelBaseStateOverrideRequest();
+        }
         if (mRequest == null) {
             return;
         }
@@ -192,11 +220,12 @@
      * notify the listener of all changes to request status as a result of this change.
      */
     void handleNewSupportedStates(int[] newSupportedStates) {
-        if (mRequest == null) {
-            return;
+        if (mBaseStateRequest != null && !contains(newSupportedStates,
+                mBaseStateRequest.getRequestedState())) {
+            cancelCurrentBaseStateRequestLocked();
         }
 
-        if (!contains(newSupportedStates, mRequest.getRequestedState())) {
+        if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
             cancelCurrentRequestLocked();
         }
     }
@@ -228,10 +257,23 @@
             return;
         }
         mStickyRequest = false;
-        mListener.onStatusChanged(mRequest, STATUS_CANCELED);
+        cancelRequestLocked(mRequest);
         mRequest = null;
     }
 
+    /**
+     * Handles cancelling {@code mBaseStateRequest}.
+     * Notifies the listener of the canceled status as well.
+     */
+    private void cancelCurrentBaseStateRequestLocked() {
+        if (mBaseStateRequest == null) {
+            Slog.w(TAG, "Attempted to cancel a null OverrideRequest");
+            return;
+        }
+        cancelRequestLocked(mBaseStateRequest);
+        mBaseStateRequest = null;
+    }
+
     private static boolean contains(int[] array, int value) {
         for (int i = 0; i < array.length; i++) {
             if (array[i] == value) {
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 2f67ddd..372bc8a 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -31,7 +31,6 @@
 import android.opengl.EGLSurface;
 import android.opengl.GLES11Ext;
 import android.opengl.GLES20;
-import android.os.IBinder;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -170,15 +169,7 @@
         mDisplayWidth = displayInfo.getNaturalWidth();
         mDisplayHeight = displayInfo.getNaturalHeight();
 
-        final IBinder token = SurfaceControl.getInternalDisplayToken();
-        if (token == null) {
-            Slog.e(TAG,
-                    "Failed to take screenshot because internal display is disconnected");
-            return false;
-        }
-        final boolean isWideColor = SurfaceControl.getDynamicDisplayInfo(token).activeColorMode
-                == Display.COLOR_MODE_DISPLAY_P3;
-
+        final boolean isWideColor = displayInfo.colorMode == Display.COLOR_MODE_DISPLAY_P3;
         // Set mPrepared here so if initialization fails, resources can be cleaned up.
         mPrepared = true;
 
diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java
index e9640cf..a060f07 100644
--- a/services/core/java/com/android/server/display/DisplayControl.java
+++ b/services/core/java/com/android/server/display/DisplayControl.java
@@ -16,6 +16,9 @@
 
 package com.android.server.display;
 
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.os.IBinder;
 
 import java.util.Objects;
@@ -26,6 +29,7 @@
 public class DisplayControl {
     private static native IBinder nativeCreateDisplay(String name, boolean secure);
     private static native void nativeDestroyDisplay(IBinder displayToken);
+    private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes);
 
     /**
      * Create a display in SurfaceFlinger.
@@ -52,4 +56,11 @@
         nativeDestroyDisplay(displayToken);
     }
 
+    /**
+     * Overrides HDR modes for a display device.
+     */
+    @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+    public static void overrideHdrTypes(@NonNull IBinder displayToken, @NonNull int[] modes) {
+        nativeOverrideHdrTypes(displayToken, modes);
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3a037ed..2cde526 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2562,10 +2562,10 @@
 
         final BrightnessSetting brightnessSetting = new BrightnessSetting(mPersistentDataStore,
                 display, mSyncRoot);
-        final DisplayPowerController displayPowerController;
+        final DisplayPowerControllerInterface displayPowerController;
 
         if (SystemProperties.getInt(PROP_USE_NEW_DISPLAY_POWER_CONTROLLER, 0) == 1) {
-            displayPowerController = new DisplayPowerController(
+            displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
                     () -> handleBrightnessChange(display));
@@ -3000,6 +3000,19 @@
             }
         }
 
+        @Override
+        public void overrideHdrTypes(int displayId, int[] modes) {
+            IBinder displayToken;
+            synchronized (mSyncRoot) {
+                displayToken = getDisplayToken(displayId);
+                if (displayToken == null) {
+                    throw new IllegalArgumentException("Invalid display: " + displayId);
+                }
+            }
+
+            DisplayControl.overrideHdrTypes(displayToken, modes);
+        }
+
         @Override // Binder call
         public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) {
             mContext.enforceCallingOrSelfPermission(
@@ -3884,6 +3897,19 @@
                 return displayIdToMirror;
             }
         }
+
+        @Override
+        public SurfaceControl.DisplayPrimaries getDisplayNativePrimaries(int displayId) {
+            IBinder displayToken;
+            synchronized (mSyncRoot) {
+                displayToken = getDisplayToken(displayId);
+                if (displayToken == null) {
+                    throw new IllegalArgumentException("Invalid displayId=" + displayId);
+                }
+            }
+
+            return SurfaceControl.getDisplayNativePrimaries(displayToken);
+        }
     }
 
     class DesiredDisplayModeSpecsObserver
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 58a80e3..61225d7 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -798,8 +798,9 @@
      * Notified when the display is changed. We use this to apply any changes that might be needed
      * when displays get swapped on foldable devices.  For example, different brightness properties
      * of each display need to be properly reflected in AutomaticBrightnessController.
+     *
+     * Make sure DisplayManagerService.mSyncRoot is held when this is called
      */
-    @GuardedBy("DisplayManagerService.mSyncRoot")
     @Override
     public void onDisplayChanged() {
         final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
@@ -988,8 +989,8 @@
                     com.android.internal.R.array.config_screenBrighteningThresholds);
             int[] screenDarkeningThresholds = resources.getIntArray(
                     com.android.internal.R.array.config_screenDarkeningThresholds);
-            int[] screenThresholdLevels = resources.getIntArray(
-                    com.android.internal.R.array.config_screenThresholdLevels);
+            float[] screenThresholdLevels = BrightnessMappingStrategy.getFloatArray(resources
+                    .obtainTypedArray(com.android.internal.R.array.config_screenThresholdLevels));
             float screenDarkeningMinThreshold =
                     mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
             float screenBrighteningMinThreshold =
@@ -1896,7 +1897,7 @@
                     }
                 },
                 () -> {
-                    sendUpdatePowerStateLocked();
+                    sendUpdatePowerState();
                     postBrightnessChangeRunnable();
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     if (mAutomaticBrightnessController != null) {
@@ -1912,7 +1913,7 @@
                 ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
         return new BrightnessThrottler(mHandler, data,
                 () -> {
-                    sendUpdatePowerStateLocked();
+                    sendUpdatePowerState();
                     postBrightnessChangeRunnable();
                 }, mUniqueDisplayId);
     }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
new file mode 100644
index 0000000..dc7db10
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -0,0 +1,3071 @@
+/*
+ * 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.display;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientBrightnessDayStats;
+import android.hardware.display.BrightnessChangeEvent;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.metrics.LogMaker;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.MutableFloat;
+import android.util.MutableInt;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.RingBuffer;
+import com.android.server.LocalServices;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
+import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.utils.SensorUtils;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
+import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings;
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the power state of the display.
+ *
+ * Handles the proximity sensor, light sensor, and animations between states
+ * including the screen off animation.
+ *
+ * This component acts independently of the rest of the power manager service.
+ * In particular, it does not share any state and it only communicates
+ * via asynchronous callbacks to inform the power manager that something has
+ * changed.
+ *
+ * Everything this class does internally is serialized on its handler although
+ * it may be accessed by other threads from the outside.
+ *
+ * Note that the power manager service guarantees that it will hold a suspend
+ * blocker as long as the display is not ready.  So most of the work done here
+ * does not need to worry about holding a suspend blocker unless it happens
+ * independently of the display ready signal.
+ *
+ * For debugging, you can make the color fade and brightness animations run
+ * slower by changing the "animator duration scale" option in Development Settings.
+ */
+final class DisplayPowerController2 implements AutomaticBrightnessController.Callbacks,
+        DisplayWhiteBalanceController.Callbacks, DisplayPowerControllerInterface {
+    private static final String SCREEN_ON_BLOCKED_TRACE_NAME = "Screen on blocked";
+    private static final String SCREEN_OFF_BLOCKED_TRACE_NAME = "Screen off blocked";
+
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+
+    // If true, uses the color fade on animation.
+    // We might want to turn this off if we cannot get a guarantee that the screen
+    // actually turns on and starts showing new content after the call to set the
+    // screen state returns.  Playing the animation can also be somewhat slow.
+    private static final boolean USE_COLOR_FADE_ON_ANIMATION = false;
+
+    private static final float SCREEN_ANIMATION_RATE_MINIMUM = 0.0f;
+
+    private static final int COLOR_FADE_ON_ANIMATION_DURATION_MILLIS = 250;
+    private static final int COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS = 400;
+
+    private static final int MSG_UPDATE_POWER_STATE = 1;
+    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
+    private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
+    private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
+    private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
+    private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
+    private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
+    private static final int MSG_IGNORE_PROXIMITY = 8;
+    private static final int MSG_STOP = 9;
+    private static final int MSG_UPDATE_BRIGHTNESS = 10;
+    private static final int MSG_UPDATE_RBC = 11;
+    private static final int MSG_BRIGHTNESS_RAMP_DONE = 12;
+    private static final int MSG_STATSD_HBM_BRIGHTNESS = 13;
+
+    private static final int PROXIMITY_UNKNOWN = -1;
+    private static final int PROXIMITY_NEGATIVE = 0;
+    private static final int PROXIMITY_POSITIVE = 1;
+
+    // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
+    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+
+    private static final int BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS = 500;
+
+    // Trigger proximity if distance is less than 5 cm.
+    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+    // State machine constants for tracking initial brightness ramp skipping when enabled.
+    private static final int RAMP_STATE_SKIP_NONE = 0;
+    private static final int RAMP_STATE_SKIP_INITIAL = 1;
+    private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2;
+
+    private static final int REPORTED_TO_POLICY_UNREPORTED = -1;
+    private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0;
+    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1;
+    private static final int REPORTED_TO_POLICY_SCREEN_ON = 2;
+    private static final int REPORTED_TO_POLICY_SCREEN_TURNING_OFF = 3;
+
+    private static final int RINGBUFFER_MAX = 100;
+
+    private final String mTag;
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+
+    // Our handler.
+    private final DisplayControllerHandler mHandler;
+
+    // Asynchronous callbacks into the power manager service.
+    // Only invoked from the handler thread while no locks are held.
+    private final DisplayPowerCallbacks mCallbacks;
+
+    // Battery stats.
+    @Nullable
+    private final IBatteryStats mBatteryStats;
+
+    // The sensor manager.
+    private final SensorManager mSensorManager;
+
+    // The window manager policy.
+    private final WindowManagerPolicy mWindowManagerPolicy;
+
+    // The display blanker.
+    private final DisplayBlanker mBlanker;
+
+    // The LogicalDisplay tied to this DisplayPowerController2.
+    private final LogicalDisplay mLogicalDisplay;
+
+    // The ID of the LogicalDisplay tied to this DisplayPowerController2.
+    private final int mDisplayId;
+
+    // The unique ID of the primary display device currently tied to this logical display
+    private String mUniqueDisplayId;
+
+    // Tracker for brightness changes.
+    @Nullable
+    private final BrightnessTracker mBrightnessTracker;
+
+    // Tracker for brightness settings changes.
+    private final SettingsObserver mSettingsObserver;
+
+    // The proximity sensor, or null if not available or needed.
+    private Sensor mProximitySensor;
+
+    // The doze screen brightness.
+    private final float mScreenBrightnessDozeConfig;
+
+    // The dim screen brightness.
+    private final float mScreenBrightnessDimConfig;
+
+    // The minimum dim amount to use if the screen brightness is already below
+    // mScreenBrightnessDimConfig.
+    private final float mScreenBrightnessMinimumDimAmount;
+
+    private final float mScreenBrightnessDefault;
+
+    // The minimum allowed brightness while in VR.
+    private final float mScreenBrightnessForVrRangeMinimum;
+
+    // The maximum allowed brightness while in VR.
+    private final float mScreenBrightnessForVrRangeMaximum;
+
+    // The default screen brightness for VR.
+    private final float mScreenBrightnessForVrDefault;
+
+    // True if auto-brightness should be used.
+    private boolean mUseSoftwareAutoBrightnessConfig;
+
+    // True if should use light sensor to automatically determine doze screen brightness.
+    private final boolean mAllowAutoBrightnessWhileDozingConfig;
+
+    // Whether or not the color fade on screen on / off is enabled.
+    private final boolean mColorFadeEnabled;
+
+    @GuardedBy("mCachedBrightnessInfo")
+    private final CachedBrightnessInfo mCachedBrightnessInfo = new CachedBrightnessInfo();
+
+    private DisplayDevice mDisplayDevice;
+
+    // True if we should fade the screen while turning it off, false if we should play
+    // a stylish color fade animation instead.
+    private final boolean mColorFadeFadesConfig;
+
+    // True if we need to fake a transition to off when coming out of a doze state.
+    // Some display hardware will blank itself when coming out of doze in order to hide
+    // artifacts. For these displays we fake a transition into OFF so that policy can appropriately
+    // blank itself and begin an appropriate power on animation.
+    private final boolean mDisplayBlanksAfterDozeConfig;
+
+    // True if there are only buckets of brightness values when the display is in the doze state,
+    // rather than a full range of values. If this is true, then we'll avoid animating the screen
+    // brightness since it'd likely be multiple jarring brightness transitions instead of just one
+    // to reach the final state.
+    private final boolean mBrightnessBucketsInDozeConfig;
+
+    private final Clock mClock;
+    private final Injector mInjector;
+
+    //  Maximum time a ramp animation can take.
+    private long mBrightnessRampIncreaseMaxTimeMillis;
+    private long mBrightnessRampDecreaseMaxTimeMillis;
+
+    // The pending power request.
+    // Initially null until the first call to requestPowerState.
+    @GuardedBy("mLock")
+    private DisplayPowerRequest mPendingRequestLocked;
+
+    // True if a request has been made to wait for the proximity sensor to go negative.
+    @GuardedBy("mLock")
+    private boolean mPendingWaitForNegativeProximityLocked;
+
+    // True if the pending power request or wait for negative proximity flag
+    // has been changed since the last update occurred.
+    @GuardedBy("mLock")
+    private boolean mPendingRequestChangedLocked;
+
+    // Set to true when the important parts of the pending power request have been applied.
+    // The important parts are mainly the screen state.  Brightness changes may occur
+    // concurrently.
+    @GuardedBy("mLock")
+    private boolean mDisplayReadyLocked;
+
+    // Set to true if a power state update is required.
+    @GuardedBy("mLock")
+    private boolean mPendingUpdatePowerStateLocked;
+
+    /* The following state must only be accessed by the handler thread. */
+
+    // The currently requested power state.
+    // The power controller will progressively update its internal state to match
+    // the requested power state.  Initially null until the first update.
+    private DisplayPowerRequest mPowerRequest;
+
+    // The current power state.
+    // Must only be accessed on the handler thread.
+    private DisplayPowerState mPowerState;
+
+    // True if the device should wait for negative proximity sensor before
+    // waking up the screen.  This is set to false as soon as a negative
+    // proximity sensor measurement is observed or when the device is forced to
+    // go to sleep by the user.  While true, the screen remains off.
+    private boolean mWaitingForNegativeProximity;
+
+    // True if the device should not take into account the proximity sensor
+    // until either the proximity sensor state changes, or there is no longer a
+    // request to listen to proximity sensor.
+    private boolean mIgnoreProximityUntilChanged;
+
+    // The actual proximity sensor threshold value.
+    private float mProximityThreshold;
+
+    // Set to true if the proximity sensor listener has been registered
+    // with the sensor manager.
+    private boolean mProximitySensorEnabled;
+
+    // The debounced proximity sensor state.
+    private int mProximity = PROXIMITY_UNKNOWN;
+
+    // The raw non-debounced proximity sensor state.
+    private int mPendingProximity = PROXIMITY_UNKNOWN;
+    private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
+
+    // True if the screen was turned off because of the proximity sensor.
+    // When the screen turns on again, we report user activity to the power manager.
+    private boolean mScreenOffBecauseOfProximity;
+
+    // The currently active screen on unblocker.  This field is non-null whenever
+    // we are waiting for a callback to release it and unblock the screen.
+    private ScreenOnUnblocker mPendingScreenOnUnblocker;
+    private ScreenOffUnblocker mPendingScreenOffUnblocker;
+
+    // True if we were in the process of turning off the screen.
+    // This allows us to recover more gracefully from situations where we abort
+    // turning off the screen.
+    private boolean mPendingScreenOff;
+
+    // True if we have unfinished business and are holding a suspend blocker.
+    private boolean mUnfinishedBusiness;
+
+    // The elapsed real time when the screen on was blocked.
+    private long mScreenOnBlockStartRealTime;
+    private long mScreenOffBlockStartRealTime;
+
+    // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_* fields.
+    private int mReportedScreenStateToPolicy = REPORTED_TO_POLICY_UNREPORTED;
+
+    // If the last recorded screen state was dozing or not.
+    private boolean mDozing;
+
+    // Remembers whether certain kinds of brightness adjustments
+    // were recently applied so that we can decide how to transition.
+    private boolean mAppliedAutoBrightness;
+    private boolean mAppliedDimming;
+    private boolean mAppliedLowPower;
+    private boolean mAppliedScreenBrightnessOverride;
+    private boolean mAppliedTemporaryBrightness;
+    private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+    private boolean mAppliedBrightnessBoost;
+    private boolean mAppliedThrottling;
+
+    // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
+    // information.
+    // At the time of this writing, this value is changed within updatePowerState() only, which is
+    // limited to the thread used by DisplayControllerHandler.
+    private final BrightnessReason mBrightnessReason = new BrightnessReason();
+    private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
+
+    // Brightness animation ramp rates in brightness units per second
+    private float mBrightnessRampRateFastDecrease;
+    private float mBrightnessRampRateFastIncrease;
+    private float mBrightnessRampRateSlowDecrease;
+    private float mBrightnessRampRateSlowIncrease;
+
+    // Report HBM brightness change to StatsD
+    private int mDisplayStatsId;
+    private float mLastStatsBrightness = PowerManager.BRIGHTNESS_MIN;
+
+    // Whether or not to skip the initial brightness ramps into STATE_ON.
+    private final boolean mSkipScreenOnBrightnessRamp;
+
+    // Display white balance components.
+    @Nullable
+    private final DisplayWhiteBalanceSettings mDisplayWhiteBalanceSettings;
+    @Nullable
+    private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
+
+    @Nullable
+    private final ColorDisplayServiceInternal mCdsi;
+    private float[] mNitsRange;
+
+    private final HighBrightnessModeController mHbmController;
+
+    private final BrightnessThrottler mBrightnessThrottler;
+
+    private final BrightnessSetting mBrightnessSetting;
+
+    private final Runnable mOnBrightnessChangeRunnable;
+
+    private final BrightnessEvent mLastBrightnessEvent;
+    private final BrightnessEvent mTempBrightnessEvent;
+
+    // Keeps a record of brightness changes for dumpsys.
+    private RingBuffer<BrightnessEvent> mBrightnessEventRingBuffer;
+
+    // A record of state for skipping brightness ramps.
+    private int mSkipRampState = RAMP_STATE_SKIP_NONE;
+
+    // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
+    private float mInitialAutoBrightness;
+
+    // The controller for the automatic brightness level.
+    @Nullable
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
+    private Sensor mLightSensor;
+
+    // The mappers between ambient lux, display backlight values, and display brightness.
+    // We will switch between the idle mapper and active mapper in AutomaticBrightnessController.
+    // Mapper used for active (normal) screen brightness mode
+    @Nullable
+    private BrightnessMappingStrategy mInteractiveModeBrightnessMapper;
+    // Mapper used for idle screen brightness mode
+    @Nullable
+    private BrightnessMappingStrategy mIdleModeBrightnessMapper;
+
+    // The current brightness configuration.
+    @Nullable
+    private BrightnessConfiguration mBrightnessConfiguration;
+
+    // The last brightness that was set by the user and not temporary. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when a brightness has yet to be recorded.
+    private float mLastUserSetScreenBrightness = Float.NaN;
+
+    // The screen brightness setting has changed but not taken effect yet. If this is different
+    // from the current screen brightness setting then this is coming from something other than us
+    // and should be considered a user interaction.
+    private float mPendingScreenBrightnessSetting;
+
+    // The last observed screen brightness setting, either set by us or by the settings app on
+    // behalf of the user.
+    private float mCurrentScreenBrightnessSetting;
+
+    // The temporary screen brightness. Typically set when a user is interacting with the
+    // brightness slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary brightness set.
+    private float mTemporaryScreenBrightness;
+
+    // The current screen brightness while in VR mode.
+    private float mScreenBrightnessForVr;
+
+    // The last auto brightness adjustment that was set by the user and not temporary. Set to
+    // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+    private float mAutoBrightnessAdjustment;
+
+    // The pending auto brightness adjustment that will take effect on the next power state update.
+    private float mPendingAutoBrightnessAdjustment;
+
+    // The temporary auto brightness adjustment. Typically set when a user is interacting with the
+    // adjustment slider but hasn't settled on a choice yet. Set to
+    // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+    private float mTemporaryAutoBrightnessAdjustment;
+
+    private boolean mIsRbcActive;
+
+    // Whether there's a callback to tell listeners the display has changed scheduled to run. When
+    // true it implies a wakelock is being held to guarantee the update happens before we collapse
+    // into suspend and so needs to be cleaned up if the thread is exiting.
+    // Should only be accessed on the Handler thread.
+    private boolean mOnStateChangedPending;
+
+    // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
+    // suspend blocker acquisitions are pending when shutting down this DPC.
+    // Should only be accessed on the Handler thread.
+    private int mOnProximityPositiveMessages;
+    private int mOnProximityNegativeMessages;
+
+    // Animators.
+    private ObjectAnimator mColorFadeOnAnimator;
+    private ObjectAnimator mColorFadeOffAnimator;
+    private DualRampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+    private BrightnessSetting.BrightnessSettingListener mBrightnessSettingListener;
+
+    // True if this DisplayPowerController2 has been stopped and should no longer be running.
+    private boolean mStopped;
+
+    private DisplayDeviceConfig mDisplayDeviceConfig;
+
+    // Identifiers for suspend blocker acuisition requests
+    private final String mSuspendBlockerIdUnfinishedBusiness;
+    private final String mSuspendBlockerIdOnStateChanged;
+    private final String mSuspendBlockerIdProxPositive;
+    private final String mSuspendBlockerIdProxNegative;
+    private final String mSuspendBlockerIdProxDebounce;
+
+    /**
+     * Creates the display power controller.
+     */
+    DisplayPowerController2(Context context, Injector injector,
+            DisplayPowerCallbacks callbacks, Handler handler,
+            SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
+            BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
+            Runnable onBrightnessChangeRunnable) {
+
+        mInjector = injector != null ? injector : new Injector();
+        mClock = mInjector.getClock();
+        mLogicalDisplay = logicalDisplay;
+        mDisplayId = mLogicalDisplay.getDisplayIdLocked();
+        mTag = "DisplayPowerController2[" + mDisplayId + "]";
+        mSuspendBlockerIdUnfinishedBusiness = getSuspendBlockerUnfinishedBusinessId(mDisplayId);
+        mSuspendBlockerIdOnStateChanged = getSuspendBlockerOnStateChangedId(mDisplayId);
+        mSuspendBlockerIdProxPositive = getSuspendBlockerProxPositiveId(mDisplayId);
+        mSuspendBlockerIdProxNegative = getSuspendBlockerProxNegativeId(mDisplayId);
+        mSuspendBlockerIdProxDebounce = getSuspendBlockerProxDebounceId(mDisplayId);
+
+        mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        mDisplayStatsId = mUniqueDisplayId.hashCode();
+        mHandler = new DisplayControllerHandler(handler.getLooper());
+        mLastBrightnessEvent = new BrightnessEvent(mDisplayId);
+        mTempBrightnessEvent = new BrightnessEvent(mDisplayId);
+
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mBatteryStats = BatteryStatsService.getService();
+        } else {
+            mBatteryStats = null;
+        }
+
+        mSettingsObserver = new SettingsObserver(mHandler);
+        mCallbacks = callbacks;
+        mSensorManager = sensorManager;
+        mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class);
+        mBlanker = blanker;
+        mContext = context;
+        mBrightnessTracker = brightnessTracker;
+        // TODO: b/186428377 update brightness setting when display changes
+        mBrightnessSetting = brightnessSetting;
+        mOnBrightnessChangeRunnable = onBrightnessChangeRunnable;
+
+        PowerManager pm = context.getSystemService(PowerManager.class);
+
+        final Resources resources = context.getResources();
+
+        // DOZE AND DIM SETTINGS
+        mScreenBrightnessDozeConfig = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
+        mScreenBrightnessDimConfig = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+        mScreenBrightnessMinimumDimAmount = resources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+
+
+        // NORMAL SCREEN SETTINGS
+        mScreenBrightnessDefault = clampAbsoluteBrightness(
+                mLogicalDisplay.getDisplayInfoLocked().brightnessDefault);
+
+        // VR SETTINGS
+        mScreenBrightnessForVrDefault = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR));
+        mScreenBrightnessForVrRangeMaximum = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR));
+        mScreenBrightnessForVrRangeMinimum = clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR));
+
+        // Check the setting, but also verify that it is the default display. Only the default
+        // display has an automatic brightness controller running.
+        // TODO: b/179021925 - Fix to work with multiple displays
+        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
+                R.bool.config_automatic_brightness_available)
+                && mDisplayId == Display.DEFAULT_DISPLAY;
+
+        mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
+                R.bool.config_allowAutoBrightnessWhileDozing);
+
+        mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
+                .getDisplayDeviceConfig();
+
+        loadBrightnessRampRates();
+        mSkipScreenOnBrightnessRamp = resources.getBoolean(
+                R.bool.config_skipScreenOnBrightnessRamp);
+
+        mHbmController = createHbmControllerLocked();
+
+        mBrightnessThrottler = createBrightnessThrottlerLocked();
+
+        // Seed the cached brightness
+        saveBrightnessInfo(getScreenBrightnessSetting());
+
+        DisplayWhiteBalanceSettings displayWhiteBalanceSettings = null;
+        DisplayWhiteBalanceController displayWhiteBalanceController = null;
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            try {
+                displayWhiteBalanceSettings = new DisplayWhiteBalanceSettings(mContext, mHandler);
+                displayWhiteBalanceController = DisplayWhiteBalanceFactory.create(mHandler,
+                        mSensorManager, resources);
+                displayWhiteBalanceSettings.setCallbacks(this);
+                displayWhiteBalanceController.setCallbacks(this);
+            } catch (Exception e) {
+                Slog.e(mTag, "failed to set up display white-balance: " + e);
+            }
+        }
+        mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings;
+        mDisplayWhiteBalanceController = displayWhiteBalanceController;
+
+        loadNitsRange(resources);
+
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class);
+            boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() {
+                @Override
+                public void onReduceBrightColorsActivationChanged(boolean activated,
+                        boolean userInitiated) {
+                    applyReduceBrightColorsSplineAdjustment();
+
+                }
+
+                @Override
+                public void onReduceBrightColorsStrengthChanged(int strength) {
+                    applyReduceBrightColorsSplineAdjustment();
+                }
+            });
+            if (active) {
+                applyReduceBrightColorsSplineAdjustment();
+            }
+        } else {
+            mCdsi = null;
+        }
+
+        setUpAutoBrightness(resources, handler);
+
+        mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic();
+        mColorFadeFadesConfig = resources.getBoolean(
+                R.bool.config_animateScreenLights);
+
+        mDisplayBlanksAfterDozeConfig = resources.getBoolean(
+                R.bool.config_displayBlanksAfterDoze);
+
+        mBrightnessBucketsInDozeConfig = resources.getBoolean(
+                R.bool.config_displayBrightnessBucketsInDoze);
+
+        loadProximitySensor();
+
+        mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+        mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+
+    }
+
+    private void applyReduceBrightColorsSplineAdjustment() {
+        mHandler.obtainMessage(MSG_UPDATE_RBC).sendToTarget();
+        sendUpdatePowerState();
+    }
+
+    private void handleRbcChanged() {
+        if (mAutomaticBrightnessController == null) {
+            return;
+        }
+        if ((!mAutomaticBrightnessController.isInIdleMode()
+                && mInteractiveModeBrightnessMapper == null)
+                || (mAutomaticBrightnessController.isInIdleMode()
+                && mIdleModeBrightnessMapper == null)) {
+            Log.w(mTag, "No brightness mapping available to recalculate splines for this mode");
+            return;
+        }
+
+        float[] adjustedNits = new float[mNitsRange.length];
+        for (int i = 0; i < mNitsRange.length; i++) {
+            adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
+        }
+        mIsRbcActive = mCdsi.isReduceBrightColorsActivated();
+        mAutomaticBrightnessController.recalculateSplines(mIsRbcActive, adjustedNits);
+    }
+
+    /**
+     * Returns true if the proximity sensor screen-off function is available.
+     */
+    @Override
+    public boolean isProximitySensorAvailable() {
+        return mProximitySensor != null;
+    }
+
+    /**
+     * Get the {@link BrightnessChangeEvent}s for the specified user.
+     *
+     * @param userId         userId to fetch data for
+     * @param includePackage if false will null out the package name in events
+     */
+    @Nullable
+    @Override
+    public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(
+            @UserIdInt int userId, boolean includePackage) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
+        return mBrightnessTracker.getEvents(userId, includePackage);
+    }
+
+    @Override
+    public void onSwitchUser(@UserIdInt int newUserId) {
+        handleSettingsChange(true /* userSwitch */);
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.onSwitchUser(newUserId);
+        }
+    }
+
+    @Nullable
+    @Override
+    public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats(
+            @UserIdInt int userId) {
+        if (mBrightnessTracker == null) {
+            return null;
+        }
+        return mBrightnessTracker.getAmbientBrightnessStats(userId);
+    }
+
+    /**
+     * Persist the brightness slider events and ambient brightness stats to disk.
+     */
+    @Override
+    public void persistBrightnessTrackerState() {
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.persistBrightnessTrackerState();
+        }
+    }
+
+    /**
+     * Requests a new power state.
+     * The controller makes a copy of the provided object and then
+     * begins adjusting the power state to match what was requested.
+     *
+     * @param request                  The requested power state.
+     * @param waitForNegativeProximity If true, issues a request to wait for
+     *                                 negative proximity before turning the screen back on,
+     *                                 assuming the screen
+     *                                 was turned off by the proximity sensor.
+     * @return True if display is ready, false if there are important changes that must
+     * be made asynchronously (such as turning the screen on), in which case the caller
+     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
+     * then try the request again later until the state converges.
+     */
+    public boolean requestPowerState(DisplayPowerRequest request,
+            boolean waitForNegativeProximity) {
+        if (DEBUG) {
+            Slog.d(mTag, "requestPowerState: "
+                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
+        }
+
+        synchronized (mLock) {
+            if (mStopped) {
+                return true;
+            }
+
+            boolean changed = false;
+
+            if (waitForNegativeProximity
+                    && !mPendingWaitForNegativeProximityLocked) {
+                mPendingWaitForNegativeProximityLocked = true;
+                changed = true;
+            }
+
+            if (mPendingRequestLocked == null) {
+                mPendingRequestLocked = new DisplayPowerRequest(request);
+                changed = true;
+            } else if (!mPendingRequestLocked.equals(request)) {
+                mPendingRequestLocked.copyFrom(request);
+                changed = true;
+            }
+
+            if (changed) {
+                mDisplayReadyLocked = false;
+                if (!mPendingRequestChangedLocked) {
+                    mPendingRequestChangedLocked = true;
+                    sendUpdatePowerStateLocked();
+                }
+            }
+
+            return mDisplayReadyLocked;
+        }
+    }
+
+    @Override
+    public BrightnessConfiguration getDefaultBrightnessConfiguration() {
+        if (mAutomaticBrightnessController == null) {
+            return null;
+        }
+        return mAutomaticBrightnessController.getDefaultConfig();
+    }
+
+    /**
+     * Notified when the display is changed. We use this to apply any changes that might be needed
+     * when displays get swapped on foldable devices.  For example, different brightness properties
+     * of each display need to be properly reflected in AutomaticBrightnessController.
+     *
+     * Make sure DisplayManagerService.mSyncRoot lock is held when this is called
+     */
+    @Override
+    public void onDisplayChanged() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        if (device == null) {
+            Slog.wtf(mTag, "Display Device is null in DisplayPowerController2 for display: "
+                    + mLogicalDisplay.getDisplayIdLocked());
+            return;
+        }
+
+        final String uniqueId = device.getUniqueId();
+        final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
+        final IBinder token = device.getDisplayTokenLocked();
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        mHandler.post(() -> {
+            if (mDisplayDevice != device) {
+                mDisplayDevice = device;
+                mUniqueDisplayId = uniqueId;
+                mDisplayStatsId = mUniqueDisplayId.hashCode();
+                mDisplayDeviceConfig = config;
+                loadFromDisplayDeviceConfig(token, info);
+                updatePowerState();
+            }
+        });
+    }
+
+    /**
+     * Called when the displays are preparing to transition from one device state to another.
+     * This process involves turning off some displays so we need updatePowerState() to run and
+     * calculate the new state.
+     */
+    @Override
+    public void onDeviceStateTransition() {
+        sendUpdatePowerState();
+    }
+
+    /**
+     * Unregisters all listeners and interrupts all running threads; halting future work.
+     *
+     * This method should be called when the DisplayPowerController2 is no longer in use; i.e. when
+     * the {@link #mDisplayId display} has been removed.
+     */
+    @Override
+    public void stop() {
+        synchronized (mLock) {
+            mStopped = true;
+            Message msg = mHandler.obtainMessage(MSG_STOP);
+            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+
+            if (mDisplayWhiteBalanceController != null) {
+                mDisplayWhiteBalanceController.setEnabled(false);
+            }
+
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.stop();
+            }
+
+            if (mBrightnessSetting != null) {
+                mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
+            }
+
+            mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+        }
+    }
+
+    private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
+        // All properties that depend on the associated DisplayDevice and the DDC must be
+        // updated here.
+        loadBrightnessRampRates();
+        loadProximitySensor();
+        loadNitsRange(mContext.getResources());
+        setUpAutoBrightness(mContext.getResources(), mHandler);
+        reloadReduceBrightColours();
+        if (mScreenBrightnessRampAnimator != null) {
+            mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+                    mBrightnessRampIncreaseMaxTimeMillis,
+                    mBrightnessRampDecreaseMaxTimeMillis);
+        }
+        mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId,
+                mDisplayDeviceConfig.getHighBrightnessModeData(),
+                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+                    @Override
+                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    }
+                });
+        mBrightnessThrottler.resetThrottlingData(
+                mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
+    }
+
+    private void sendUpdatePowerState() {
+        synchronized (mLock) {
+            sendUpdatePowerStateLocked();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void sendUpdatePowerStateLocked() {
+        if (!mStopped && !mPendingUpdatePowerStateLocked) {
+            mPendingUpdatePowerStateLocked = true;
+            Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
+            mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
+        }
+    }
+
+    private void initialize(int displayState) {
+        mPowerState = mInjector.getDisplayPowerState(mBlanker,
+                mColorFadeEnabled ? new ColorFade(mDisplayId) : null, mDisplayId, displayState);
+
+        if (mColorFadeEnabled) {
+            mColorFadeOnAnimator = ObjectAnimator.ofFloat(
+                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 0.0f, 1.0f);
+            mColorFadeOnAnimator.setDuration(COLOR_FADE_ON_ANIMATION_DURATION_MILLIS);
+            mColorFadeOnAnimator.addListener(mAnimatorListener);
+
+            mColorFadeOffAnimator = ObjectAnimator.ofFloat(
+                    mPowerState, DisplayPowerState.COLOR_FADE_LEVEL, 1.0f, 0.0f);
+            mColorFadeOffAnimator.setDuration(COLOR_FADE_OFF_ANIMATION_DURATION_MILLIS);
+            mColorFadeOffAnimator.addListener(mAnimatorListener);
+        }
+
+        mScreenBrightnessRampAnimator = mInjector.getDualRampAnimator(mPowerState,
+                DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
+                DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
+        mScreenBrightnessRampAnimator.setAnimationTimeLimits(
+                mBrightnessRampIncreaseMaxTimeMillis,
+                mBrightnessRampDecreaseMaxTimeMillis);
+        mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
+
+        noteScreenState(mPowerState.getScreenState());
+        noteScreenBrightness(mPowerState.getScreenBrightness());
+
+        // Initialize all of the brightness tracking state
+        final float brightness = convertToNits(mPowerState.getScreenBrightness());
+        if (brightness >= PowerManager.BRIGHTNESS_MIN) {
+            mBrightnessTracker.start(brightness);
+        }
+        mBrightnessSettingListener = brightnessValue -> {
+            Message msg = mHandler.obtainMessage(MSG_UPDATE_BRIGHTNESS, brightnessValue);
+            mHandler.sendMessage(msg);
+        };
+
+        mBrightnessSetting.registerListener(mBrightnessSettingListener);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+                false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+    }
+
+    private void setUpAutoBrightness(Resources resources, Handler handler) {
+        if (!mUseSoftwareAutoBrightnessConfig) {
+            return;
+        }
+
+        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+                R.bool.config_enableIdleScreenBrightnessMode);
+        mInteractiveModeBrightnessMapper = BrightnessMappingStrategy.create(resources,
+                mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+        if (isIdleScreenBrightnessEnabled) {
+            mIdleModeBrightnessMapper = BrightnessMappingStrategy.createForIdleMode(resources,
+                    mDisplayDeviceConfig, mDisplayWhiteBalanceController);
+        }
+
+        if (mInteractiveModeBrightnessMapper != null) {
+            final float dozeScaleFactor = resources.getFraction(
+                    R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+                    1, 1);
+
+            int[] ambientBrighteningThresholds = resources.getIntArray(
+                    R.array.config_ambientBrighteningThresholds);
+            int[] ambientDarkeningThresholds = resources.getIntArray(
+                    R.array.config_ambientDarkeningThresholds);
+            int[] ambientThresholdLevels = resources.getIntArray(
+                    R.array.config_ambientThresholdLevels);
+            float ambientDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
+            float ambientBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
+            HysteresisLevels ambientBrightnessThresholds = new HysteresisLevels(
+                    ambientBrighteningThresholds, ambientDarkeningThresholds,
+                    ambientThresholdLevels, ambientDarkeningMinThreshold,
+                    ambientBrighteningMinThreshold);
+
+            int[] screenBrighteningThresholds = resources.getIntArray(
+                    R.array.config_screenBrighteningThresholds);
+            int[] screenDarkeningThresholds = resources.getIntArray(
+                    R.array.config_screenDarkeningThresholds);
+            int[] screenThresholdLevels = resources.getIntArray(
+                    R.array.config_screenThresholdLevels);
+            float screenDarkeningMinThreshold =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
+            float screenBrighteningMinThreshold =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
+            HysteresisLevels screenBrightnessThresholds = new HysteresisLevels(
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+                    screenDarkeningMinThreshold, screenBrighteningMinThreshold);
+
+            // Idle screen thresholds
+            float screenDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
+            float screenBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
+            HysteresisLevels screenBrightnessThresholdsIdle = new HysteresisLevels(
+                    screenBrighteningThresholds, screenDarkeningThresholds, screenThresholdLevels,
+                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+
+            // Idle ambient thresholds
+            float ambientDarkeningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
+            float ambientBrighteningMinThresholdIdle =
+                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
+            HysteresisLevels ambientBrightnessThresholdsIdle = new HysteresisLevels(
+                    ambientBrighteningThresholds, ambientDarkeningThresholds,
+                    ambientThresholdLevels, ambientDarkeningMinThresholdIdle,
+                    ambientBrighteningMinThresholdIdle);
+
+            long brighteningLightDebounce = mDisplayDeviceConfig
+                    .getAutoBrightnessBrighteningLightDebounce();
+            long darkeningLightDebounce = mDisplayDeviceConfig
+                    .getAutoBrightnessDarkeningLightDebounce();
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+                    R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
+
+            int lightSensorWarmUpTimeConfig = resources.getInteger(
+                    R.integer.config_lightSensorWarmupTime);
+            int lightSensorRate = resources.getInteger(
+                    R.integer.config_autoBrightnessLightSensorRate);
+            int initialLightSensorRate = resources.getInteger(
+                    R.integer.config_autoBrightnessInitialLightSensorRate);
+            if (initialLightSensorRate == -1) {
+                initialLightSensorRate = lightSensorRate;
+            } else if (initialLightSensorRate > lightSensorRate) {
+                Slog.w(mTag, "Expected config_autoBrightnessInitialLightSensorRate ("
+                        + initialLightSensorRate + ") to be less than or equal to "
+                        + "config_autoBrightnessLightSensorRate (" + lightSensorRate + ").");
+            }
+
+            loadAmbientLightSensor();
+            if (mBrightnessTracker != null) {
+                mBrightnessTracker.setLightSensor(mLightSensor);
+            }
+
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.stop();
+            }
+            mAutomaticBrightnessController = new AutomaticBrightnessController(this,
+                    handler.getLooper(), mSensorManager, mLightSensor,
+                    mInteractiveModeBrightnessMapper, lightSensorWarmUpTimeConfig,
+                    PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, dozeScaleFactor,
+                    lightSensorRate, initialLightSensorRate, brighteningLightDebounce,
+                    darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp,
+                    ambientBrightnessThresholds, screenBrightnessThresholds,
+                    ambientBrightnessThresholdsIdle, screenBrightnessThresholdsIdle, mContext,
+                    mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
+                    mDisplayDeviceConfig.getAmbientHorizonShort(),
+                    mDisplayDeviceConfig.getAmbientHorizonLong());
+
+            mBrightnessEventRingBuffer =
+                    new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
+        } else {
+            mUseSoftwareAutoBrightnessConfig = false;
+        }
+    }
+
+    private void loadBrightnessRampRates() {
+        mBrightnessRampRateFastDecrease = mDisplayDeviceConfig.getBrightnessRampFastDecrease();
+        mBrightnessRampRateFastIncrease = mDisplayDeviceConfig.getBrightnessRampFastIncrease();
+        mBrightnessRampRateSlowDecrease = mDisplayDeviceConfig.getBrightnessRampSlowDecrease();
+        mBrightnessRampRateSlowIncrease = mDisplayDeviceConfig.getBrightnessRampSlowIncrease();
+        mBrightnessRampDecreaseMaxTimeMillis =
+                mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis();
+        mBrightnessRampIncreaseMaxTimeMillis =
+                mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis();
+    }
+
+    private void loadNitsRange(Resources resources) {
+        if (mDisplayDeviceConfig != null && mDisplayDeviceConfig.getNits() != null) {
+            mNitsRange = mDisplayDeviceConfig.getNits();
+        } else {
+            Slog.w(mTag, "Screen brightness nits configuration is unavailable; falling back");
+            mNitsRange = BrightnessMappingStrategy.getFloatArray(resources
+                    .obtainTypedArray(R.array.config_screenBrightnessNits));
+        }
+    }
+
+    private void reloadReduceBrightColours() {
+        if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
+            applyReduceBrightColorsSplineAdjustment();
+        }
+    }
+
+    @Override
+    public void setAutomaticScreenBrightnessMode(boolean isIdle) {
+        if (mAutomaticBrightnessController != null) {
+            if (isIdle) {
+                mAutomaticBrightnessController.switchToIdleMode();
+            } else {
+                mAutomaticBrightnessController.switchToInteractiveScreenBrightnessMode();
+            }
+        }
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setStrongModeEnabled(isIdle);
+        }
+    }
+
+    private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            sendUpdatePowerState();
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+    };
+
+    private final RampAnimator.Listener mRampAnimatorListener = new RampAnimator.Listener() {
+        @Override
+        public void onAnimationEnd() {
+            sendUpdatePowerState();
+            Message msg = mHandler.obtainMessage(MSG_BRIGHTNESS_RAMP_DONE);
+            mHandler.sendMessage(msg);
+        }
+    };
+
+    /** Clean up all resources that are accessed via the {@link #mHandler} thread. */
+    private void cleanupHandlerThreadAfterStop() {
+        setProximitySensorEnabled(false);
+        mHbmController.stop();
+        mBrightnessThrottler.stop();
+        mHandler.removeCallbacksAndMessages(null);
+
+        // Release any outstanding wakelocks we're still holding because of pending messages.
+        if (mUnfinishedBusiness) {
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+            mUnfinishedBusiness = false;
+        }
+        if (mOnStateChangedPending) {
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            mOnStateChangedPending = false;
+        }
+        for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+        }
+        mOnProximityPositiveMessages = 0;
+        for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+        }
+        mOnProximityNegativeMessages = 0;
+
+        final float brightness = mPowerState != null
+                ? mPowerState.getScreenBrightness()
+                : PowerManager.BRIGHTNESS_MIN;
+        reportStats(brightness);
+
+        if (mPowerState != null) {
+            mPowerState.stop();
+            mPowerState = null;
+        }
+    }
+
+    private void updatePowerState() {
+        if (DEBUG) {
+            Trace.beginSection("DisplayPowerController#updatePowerState");
+        }
+        updatePowerStateInternal();
+        if (DEBUG) {
+            Trace.endSection();
+        }
+    }
+
+    private void updatePowerStateInternal() {
+        // Update the power state request.
+        final boolean mustNotify;
+        final int previousPolicy;
+        boolean mustInitialize = false;
+        int brightnessAdjustmentFlags = 0;
+        mBrightnessReasonTemp.set(null);
+        mTempBrightnessEvent.reset();
+        synchronized (mLock) {
+            if (mStopped) {
+                return;
+            }
+            mPendingUpdatePowerStateLocked = false;
+            if (mPendingRequestLocked == null) {
+                return; // wait until first actual power request
+            }
+
+            if (mPowerRequest == null) {
+                mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
+                updatePendingProximityRequestsLocked();
+                mPendingRequestChangedLocked = false;
+                mustInitialize = true;
+                // Assume we're on and bright until told otherwise, since that's the state we turn
+                // on in.
+                previousPolicy = DisplayPowerRequest.POLICY_BRIGHT;
+            } else if (mPendingRequestChangedLocked) {
+                previousPolicy = mPowerRequest.policy;
+                mPowerRequest.copyFrom(mPendingRequestLocked);
+                updatePendingProximityRequestsLocked();
+                mPendingRequestChangedLocked = false;
+                mDisplayReadyLocked = false;
+            } else {
+                previousPolicy = mPowerRequest.policy;
+            }
+
+            mustNotify = !mDisplayReadyLocked;
+        }
+
+        // Compute the basic display state using the policy.
+        // We might override this below based on other factors.
+        // Initialise brightness as invalid.
+        int state;
+        float brightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        boolean performScreenOffTransition = false;
+        switch (mPowerRequest.policy) {
+            case DisplayPowerRequest.POLICY_OFF:
+                state = Display.STATE_OFF;
+                performScreenOffTransition = true;
+                break;
+            case DisplayPowerRequest.POLICY_DOZE:
+                if (mPowerRequest.dozeScreenState != Display.STATE_UNKNOWN) {
+                    state = mPowerRequest.dozeScreenState;
+                } else {
+                    state = Display.STATE_DOZE;
+                }
+                if (!mAllowAutoBrightnessWhileDozingConfig) {
+                    brightnessState = mPowerRequest.dozeScreenBrightness;
+                    mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE);
+                }
+                break;
+            case DisplayPowerRequest.POLICY_VR:
+                state = Display.STATE_VR;
+                break;
+            case DisplayPowerRequest.POLICY_DIM:
+            case DisplayPowerRequest.POLICY_BRIGHT:
+            default:
+                state = Display.STATE_ON;
+                break;
+        }
+        assert (state != Display.STATE_UNKNOWN);
+
+        boolean skipRampBecauseOfProximityChangeToNegative = false;
+        // Apply the proximity sensor.
+        if (mProximitySensor != null) {
+            if (mPowerRequest.useProximitySensor && state != Display.STATE_OFF) {
+                // At this point the policy says that the screen should be on, but we've been
+                // asked to listen to the prox sensor to adjust the display state, so lets make
+                // sure the sensor is on.
+                setProximitySensorEnabled(true);
+                if (!mScreenOffBecauseOfProximity
+                        && mProximity == PROXIMITY_POSITIVE
+                        && !mIgnoreProximityUntilChanged) {
+                    // Prox sensor already reporting "near" so we should turn off the screen.
+                    // Also checked that we aren't currently set to ignore the proximity sensor
+                    // temporarily.
+                    mScreenOffBecauseOfProximity = true;
+                    sendOnProximityPositiveWithWakelock();
+                }
+            } else if (mWaitingForNegativeProximity
+                    && mScreenOffBecauseOfProximity
+                    && mProximity == PROXIMITY_POSITIVE
+                    && state != Display.STATE_OFF) {
+                // The policy says that we should have the screen on, but it's off due to the prox
+                // and we've been asked to wait until the screen is far from the user to turn it
+                // back on. Let keep the prox sensor on so we can tell when it's far again.
+                setProximitySensorEnabled(true);
+            } else {
+                // We haven't been asked to use the prox sensor and we're not waiting on the screen
+                // to turn back on...so lets shut down the prox sensor.
+                setProximitySensorEnabled(false);
+                mWaitingForNegativeProximity = false;
+            }
+
+            if (mScreenOffBecauseOfProximity
+                    && (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
+                // The screen *was* off due to prox being near, but now it's "far" so lets turn
+                // the screen back on.  Also turn it back on if we've been asked to ignore the
+                // prox sensor temporarily.
+                mScreenOffBecauseOfProximity = false;
+                skipRampBecauseOfProximityChangeToNegative = true;
+                sendOnProximityNegativeWithWakelock();
+            }
+        } else {
+            mWaitingForNegativeProximity = false;
+            mIgnoreProximityUntilChanged = false;
+        }
+
+        if (!mLogicalDisplay.isEnabled()
+                || mLogicalDisplay.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
+                || mScreenOffBecauseOfProximity) {
+            state = Display.STATE_OFF;
+        }
+
+        // Initialize things the first time the power state is changed.
+        if (mustInitialize) {
+            initialize(state);
+        }
+
+        // Animate the screen state change unless already animating.
+        // The transition may be deferred, so after this point we will use the
+        // actual state instead of the desired one.
+        final int oldState = mPowerState.getScreenState();
+        animateScreenStateChange(state, performScreenOffTransition);
+        state = mPowerState.getScreenState();
+
+        if (state == Display.STATE_OFF) {
+            brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
+        }
+
+        // Always use the VR brightness when in the VR state.
+        if (state == Display.STATE_VR) {
+            brightnessState = mScreenBrightnessForVr;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_VR);
+        }
+
+        if ((Float.isNaN(brightnessState))
+                && isValidBrightnessValue(mPowerRequest.screenBrightnessOverride)) {
+            brightnessState = mPowerRequest.screenBrightnessOverride;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_OVERRIDE);
+            mAppliedScreenBrightnessOverride = true;
+        } else {
+            mAppliedScreenBrightnessOverride = false;
+        }
+
+        final boolean autoBrightnessEnabledInDoze =
+                mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
+        final boolean autoBrightnessEnabled = mPowerRequest.useAutoBrightness
+                && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
+                && Float.isNaN(brightnessState)
+                && mAutomaticBrightnessController != null;
+        final boolean autoBrightnessDisabledDueToDisplayOff = mPowerRequest.useAutoBrightness
+                && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
+        final int autoBrightnessState = autoBrightnessEnabled
+                ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+                : autoBrightnessDisabledDueToDisplayOff
+                        ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+                        : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+        final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+
+        // Use the temporary screen brightness if there isn't an override, either from
+        // WindowManager or based on the display state.
+        if (isValidBrightnessValue(mTemporaryScreenBrightness)) {
+            brightnessState = mTemporaryScreenBrightness;
+            mAppliedTemporaryBrightness = true;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_TEMPORARY);
+        } else {
+            mAppliedTemporaryBrightness = false;
+        }
+
+        final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
+
+        // Use the autobrightness adjustment override if set.
+        final float autoBrightnessAdjustment;
+        if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
+            autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
+            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
+            mAppliedTemporaryAutoBrightnessAdjustment = true;
+        } else {
+            autoBrightnessAdjustment = mAutoBrightnessAdjustment;
+            brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
+            mAppliedTemporaryAutoBrightnessAdjustment = false;
+        }
+        // Apply brightness boost.
+        // We do this here after deciding whether auto-brightness is enabled so that we don't
+        // disable the light sensor during this temporary state.  That way when boost ends we will
+        // be able to resume normal auto-brightness behavior without any delay.
+        if (mPowerRequest.boostScreenBrightness
+                && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
+            brightnessState = PowerManager.BRIGHTNESS_MAX;
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
+            mAppliedBrightnessBoost = true;
+        } else {
+            mAppliedBrightnessBoost = false;
+        }
+
+        // If the brightness is already set then it's been overridden by something other than the
+        // user, or is a temporary adjustment.
+        boolean userInitiatedChange = (Float.isNaN(brightnessState))
+                && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
+        boolean hadUserBrightnessPoint = false;
+        // Configure auto-brightness.
+        if (mAutomaticBrightnessController != null) {
+            hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints();
+            mAutomaticBrightnessController.configure(autoBrightnessState,
+                    mBrightnessConfiguration,
+                    mLastUserSetScreenBrightness,
+                    userSetBrightnessChanged, autoBrightnessAdjustment,
+                    autoBrightnessAdjustmentChanged, mPowerRequest.policy);
+        }
+
+        if (mBrightnessTracker != null) {
+            mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
+        }
+
+        boolean updateScreenBrightnessSetting = false;
+
+        // Apply auto-brightness.
+        boolean slowChange = false;
+        if (Float.isNaN(brightnessState)) {
+            float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
+            if (autoBrightnessEnabled) {
+                brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
+                        mTempBrightnessEvent);
+                newAutoBrightnessAdjustment =
+                        mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
+            }
+            if (isValidBrightnessValue(brightnessState)
+                    || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+                // Use current auto-brightness value and slowly adjust to changes.
+                brightnessState = clampScreenBrightness(brightnessState);
+                if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
+                    slowChange = true; // slowly adapt to auto-brightness
+                }
+                updateScreenBrightnessSetting = mCurrentScreenBrightnessSetting != brightnessState;
+                mAppliedAutoBrightness = true;
+                mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+            } else {
+                mAppliedAutoBrightness = false;
+            }
+            if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+                // If the autobrightness controller has decided to change the adjustment value
+                // used, make sure that's reflected in settings.
+                putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+            } else {
+                // Adjustment values resulted in no change
+                brightnessAdjustmentFlags = 0;
+            }
+        } else {
+            // Any non-auto-brightness values such as override or temporary should still be subject
+            // to clamping so that they don't go beyond the current max as specified by HBM
+            // Controller.
+            brightnessState = clampScreenBrightness(brightnessState);
+            mAppliedAutoBrightness = false;
+            brightnessAdjustmentFlags = 0;
+        }
+
+        // Use default brightness when dozing unless overridden.
+        if ((Float.isNaN(brightnessState))
+                && Display.isDozeState(state)) {
+            brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig);
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
+        }
+
+        // Apply manual brightness.
+        if (Float.isNaN(brightnessState)) {
+            brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+            if (brightnessState != mCurrentScreenBrightnessSetting) {
+                // The manually chosen screen brightness is outside of the currently allowed
+                // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+                // by updating the setting.
+                updateScreenBrightnessSetting = true;
+            }
+            mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
+        }
+
+        // Now that a desired brightness has been calculated, apply brightness throttling. The
+        // dimming and low power transformations that follow can only dim brightness further.
+        //
+        // We didn't do this earlier through brightness clamping because we need to know both
+        // unthrottled (unclamped/ideal) and throttled brightness levels for subsequent operations.
+        // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
+        // we broadcast this change through setting.
+        final float unthrottledBrightnessState = brightnessState;
+        if (mBrightnessThrottler.isThrottled()) {
+            mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
+            brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
+            mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_THROTTLED);
+            if (!mAppliedThrottling) {
+                // Brightness throttling is needed, so do so quickly.
+                // Later, when throttling is removed, we let other mechanisms decide on speed.
+                slowChange = false;
+            }
+            mAppliedThrottling = true;
+        } else if (mAppliedThrottling) {
+            mAppliedThrottling = false;
+        }
+
+        if (updateScreenBrightnessSetting) {
+            // Tell the rest of the system about the new brightness in case we had to change it
+            // for things like auto-brightness or high-brightness-mode. Note that we do this
+            // before applying the low power or dim transformations so that the slider
+            // accurately represents the full possible range, even if they range changes what
+            // it means in absolute terms.
+            updateScreenBrightnessSetting(brightnessState);
+        }
+
+        // Apply dimming by at least some minimum amount when user activity
+        // timeout is about to expire.
+        if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
+            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+                brightnessState = Math.max(
+                        Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
+                                mScreenBrightnessDimConfig),
+                        PowerManager.BRIGHTNESS_MIN);
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
+            }
+            if (!mAppliedDimming) {
+                slowChange = false;
+            }
+            mAppliedDimming = true;
+        } else if (mAppliedDimming) {
+            slowChange = false;
+            mAppliedDimming = false;
+        }
+        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+        // as long as it is above the minimum threshold.
+        if (mPowerRequest.lowPowerMode) {
+            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
+                final float brightnessFactor =
+                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
+                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
+                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
+                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
+            }
+            if (!mAppliedLowPower) {
+                slowChange = false;
+            }
+            mAppliedLowPower = true;
+        } else if (mAppliedLowPower) {
+            slowChange = false;
+            mAppliedLowPower = false;
+        }
+
+        // The current brightness to use has been calculated at this point, and HbmController should
+        // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
+        // here instead of having HbmController listen to the brightness setting because certain
+        // brightness sources (such as an app override) are not saved to the setting, but should be
+        // reflected in HBM calculations.
+        mHbmController.onBrightnessChanged(brightnessState, unthrottledBrightnessState,
+                mBrightnessThrottler.getBrightnessMaxReason());
+
+        // Animate the screen brightness when the screen is on or dozing.
+        // Skip the animation when the screen is off or suspended or transition to/from VR.
+        boolean brightnessAdjusted = false;
+        final boolean brightnessIsTemporary =
+                mAppliedTemporaryBrightness || mAppliedTemporaryAutoBrightnessAdjustment;
+        if (!mPendingScreenOff) {
+            if (mSkipScreenOnBrightnessRamp) {
+                if (state == Display.STATE_ON) {
+                    if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) {
+                        mInitialAutoBrightness = brightnessState;
+                        mSkipRampState = RAMP_STATE_SKIP_INITIAL;
+                    } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL
+                            && mUseSoftwareAutoBrightnessConfig
+                            && !BrightnessSynchronizer.floatEquals(brightnessState,
+                            mInitialAutoBrightness)) {
+                        mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT;
+                    } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) {
+                        mSkipRampState = RAMP_STATE_SKIP_NONE;
+                    }
+                } else {
+                    mSkipRampState = RAMP_STATE_SKIP_NONE;
+                }
+            }
+
+            final boolean wasOrWillBeInVr =
+                    (state == Display.STATE_VR || oldState == Display.STATE_VR);
+            final boolean initialRampSkip = (state == Display.STATE_ON && mSkipRampState
+                    != RAMP_STATE_SKIP_NONE) || skipRampBecauseOfProximityChangeToNegative;
+            // While dozing, sometimes the brightness is split into buckets. Rather than animating
+            // through the buckets, which is unlikely to be smooth in the first place, just jump
+            // right to the suggested brightness.
+            final boolean hasBrightnessBuckets =
+                    Display.isDozeState(state) && mBrightnessBucketsInDozeConfig;
+            // If the color fade is totally covering the screen then we can change the backlight
+            // level without it being a noticeable jump since any actual content isn't yet visible.
+            final boolean isDisplayContentVisible =
+                    mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
+            // We only want to animate the brightness if it is between 0.0f and 1.0f.
+            // brightnessState can contain the values -1.0f and NaN, which we do not want to
+            // animate to. To avoid this, we check the value first.
+            // If the brightnessState is off (-1.0f) we still want to animate to the minimum
+            // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
+            // user even when the display is all black. We also clamp here in case some
+            // transformations to the brightness have pushed it outside of the currently
+            // allowed range.
+            float animateValue = clampScreenBrightness(brightnessState);
+
+            // If there are any HDR layers on the screen, we have a special brightness value that we
+            // use instead. We still preserve the calculated brightness for Standard Dynamic Range
+            // (SDR) layers, but the main brightness value will be the one for HDR.
+            float sdrAnimateValue = animateValue;
+            // TODO(b/216365040): The decision to prevent HBM for HDR in low power mode should be
+            // done in HighBrightnessModeController.
+            if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+                    && ((mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_DIMMED) == 0
+                    || (mBrightnessReason.getModifier() & BrightnessReason.MODIFIER_LOW_POWER)
+                    == 0)) {
+                // We want to scale HDR brightness level with the SDR level
+                animateValue = mHbmController.getHdrBrightnessValue();
+            }
+
+            final float currentBrightness = mPowerState.getScreenBrightness();
+            final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+            if (isValidBrightnessValue(animateValue)
+                    && (animateValue != currentBrightness
+                    || sdrAnimateValue != currentSdrBrightness)) {
+                if (initialRampSkip || hasBrightnessBuckets
+                        || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
+                    animateScreenBrightness(animateValue, sdrAnimateValue,
+                            SCREEN_ANIMATION_RATE_MINIMUM);
+                } else {
+                    boolean isIncreasing = animateValue > currentBrightness;
+                    final float rampSpeed;
+                    if (isIncreasing && slowChange) {
+                        rampSpeed = mBrightnessRampRateSlowIncrease;
+                    } else if (isIncreasing && !slowChange) {
+                        rampSpeed = mBrightnessRampRateFastIncrease;
+                    } else if (!isIncreasing && slowChange) {
+                        rampSpeed = mBrightnessRampRateSlowDecrease;
+                    } else {
+                        rampSpeed = mBrightnessRampRateFastDecrease;
+                    }
+                    animateScreenBrightness(animateValue, sdrAnimateValue, rampSpeed);
+                }
+            }
+
+            // Report brightness to brightnesstracker:
+            // If brightness is not temporary (ie the slider has been released)
+            // AND if we are not in idle screen brightness mode.
+            if (!brightnessIsTemporary
+                    && (mAutomaticBrightnessController != null
+                    && !mAutomaticBrightnessController.isInIdleMode())) {
+                if (userInitiatedChange && (mAutomaticBrightnessController == null
+                        || !mAutomaticBrightnessController.hasValidAmbientLux())) {
+                    // If we don't have a valid lux reading we can't report a valid
+                    // slider event so notify as if the system changed the brightness.
+                    userInitiatedChange = false;
+                }
+                notifyBrightnessTrackerChanged(brightnessState, userInitiatedChange,
+                        hadUserBrightnessPoint);
+            }
+
+            // We save the brightness info *after* the brightness setting has been changed and
+            // adjustments made so that the brightness info reflects the latest value.
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+        } else {
+            brightnessAdjusted = saveBrightnessInfo(getScreenBrightnessSetting());
+        }
+
+        // Only notify if the brightness adjustment is not temporary (i.e. slider has been released)
+        if (brightnessAdjusted && !brightnessIsTemporary) {
+            postBrightnessChangeRunnable();
+        }
+
+        // Log any changes to what is currently driving the brightness setting.
+        if (!mBrightnessReasonTemp.equals(mBrightnessReason) || brightnessAdjustmentFlags != 0) {
+            Slog.v(mTag, "Brightness [" + brightnessState + "] reason changing to: '"
+                    + mBrightnessReasonTemp.toString(brightnessAdjustmentFlags)
+                    + "', previous reason: '" + mBrightnessReason + "'.");
+            mBrightnessReason.set(mBrightnessReasonTemp);
+        } else if (mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_MANUAL
+                && userSetBrightnessChanged) {
+            Slog.v(mTag, "Brightness [" + brightnessState + "] manual adjustment.");
+        }
+
+
+        // Log brightness events when a detail of significance has changed. Generally this is the
+        // brightness itself changing, but also includes data like HBM cap, thermal throttling
+        // brightness cap, RBC state, etc.
+        mTempBrightnessEvent.setTime(System.currentTimeMillis());
+        mTempBrightnessEvent.setBrightness(brightnessState);
+        mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
+        mTempBrightnessEvent.setReason(mBrightnessReason);
+        mTempBrightnessEvent.setHbmMax(mHbmController.getCurrentBrightnessMax());
+        mTempBrightnessEvent.setHbmMode(mHbmController.getHighBrightnessMode());
+        mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+                | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
+                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+        mTempBrightnessEvent.setRbcStrength(mCdsi != null
+                ? mCdsi.getReduceBrightColorsStrength() : -1);
+        mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+        // Temporary is what we use during slider interactions. We avoid logging those so that
+        // we don't spam logcat when the slider is being used.
+        boolean tempToTempTransition =
+                mTempBrightnessEvent.getReason().getReason() == BrightnessReason.REASON_TEMPORARY
+                        && mLastBrightnessEvent.getReason().getReason()
+                        == BrightnessReason.REASON_TEMPORARY;
+        if ((!mTempBrightnessEvent.equalsMainData(mLastBrightnessEvent) && !tempToTempTransition)
+                || brightnessAdjustmentFlags != 0) {
+            float lastBrightness = mLastBrightnessEvent.getBrightness();
+            mTempBrightnessEvent.setInitialBrightness(lastBrightness);
+            mTempBrightnessEvent.setFastAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
+            mTempBrightnessEvent.setSlowAmbientLux(
+                    mAutomaticBrightnessController == null
+                        ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
+            mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
+            mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
+            BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
+            // Adjustment flags (and user-set flag) only get added after the equality checks since
+            // they are transient.
+            newEvent.setAdjustmentFlags(brightnessAdjustmentFlags);
+            newEvent.setFlags(newEvent.getFlags() | (userSetBrightnessChanged
+                    ? BrightnessEvent.FLAG_USER_SET : 0));
+            Slog.i(mTag, newEvent.toString(/* includeTime= */ false));
+
+            if (userSetBrightnessChanged) {
+                logManualBrightnessEvent(newEvent);
+            }
+            if (mBrightnessEventRingBuffer != null) {
+                mBrightnessEventRingBuffer.append(newEvent);
+            }
+        }
+
+        // Update display white-balance.
+        if (mDisplayWhiteBalanceController != null) {
+            if (state == Display.STATE_ON && mDisplayWhiteBalanceSettings.isEnabled()) {
+                mDisplayWhiteBalanceController.setEnabled(true);
+                mDisplayWhiteBalanceController.updateDisplayColorTemperature();
+            } else {
+                mDisplayWhiteBalanceController.setEnabled(false);
+            }
+        }
+
+        // Determine whether the display is ready for use in the newly requested state.
+        // Note that we do not wait for the brightness ramp animation to complete before
+        // reporting the display is ready because we only need to ensure the screen is in the
+        // right power state even as it continues to converge on the desired brightness.
+        final boolean ready = mPendingScreenOnUnblocker == null
+                && (!mColorFadeEnabled || (!mColorFadeOnAnimator.isStarted()
+                        && !mColorFadeOffAnimator.isStarted()))
+                && mPowerState.waitUntilClean(mCleanListener);
+        final boolean finished = ready
+                && !mScreenBrightnessRampAnimator.isAnimating();
+
+        // Notify policy about screen turned on.
+        if (ready && state != Display.STATE_OFF
+                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_ON) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_ON);
+            mWindowManagerPolicy.screenTurnedOn(mDisplayId);
+        }
+
+        // Grab a wake lock if we have unfinished business.
+        if (!finished && !mUnfinishedBusiness) {
+            if (DEBUG) {
+                Slog.d(mTag, "Unfinished business...");
+            }
+            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+            mUnfinishedBusiness = true;
+        }
+
+        // Notify the power manager when ready.
+        if (ready && mustNotify) {
+            // Send state change.
+            synchronized (mLock) {
+                if (!mPendingRequestChangedLocked) {
+                    mDisplayReadyLocked = true;
+
+                    if (DEBUG) {
+                        Slog.d(mTag, "Display ready!");
+                    }
+                }
+            }
+            sendOnStateChangedWithWakelock();
+        }
+
+        // Release the wake lock when we have no unfinished business.
+        if (finished && mUnfinishedBusiness) {
+            if (DEBUG) {
+                Slog.d(mTag, "Finished business...");
+            }
+            mUnfinishedBusiness = false;
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
+        }
+
+        // Record if dozing for future comparison.
+        mDozing = state != Display.STATE_ON;
+
+        if (previousPolicy != mPowerRequest.policy) {
+            logDisplayPolicyChanged(mPowerRequest.policy);
+        }
+    }
+
+    @Override
+    public void updateBrightness() {
+        sendUpdatePowerState();
+    }
+
+    /**
+     * Ignores the proximity sensor until the sensor state changes, but only if the sensor is
+     * currently enabled and forcing the screen to be dark.
+     */
+    @Override
+    public void ignoreProximitySensorUntilChanged() {
+        mHandler.sendEmptyMessage(MSG_IGNORE_PROXIMITY);
+    }
+
+    @Override
+    public void setBrightnessConfiguration(BrightnessConfiguration c) {
+        Message msg = mHandler.obtainMessage(MSG_CONFIGURE_BRIGHTNESS, c);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public void setTemporaryBrightness(float brightness) {
+        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
+                Float.floatToIntBits(brightness), 0 /*unused*/);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+        Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
+                Float.floatToIntBits(adjustment), 0 /*unused*/);
+        msg.sendToTarget();
+    }
+
+    @Override
+    public BrightnessInfo getBrightnessInfo() {
+        synchronized (mCachedBrightnessInfo) {
+            return new BrightnessInfo(
+                    mCachedBrightnessInfo.brightness.value,
+                    mCachedBrightnessInfo.adjustedBrightness.value,
+                    mCachedBrightnessInfo.brightnessMin.value,
+                    mCachedBrightnessInfo.brightnessMax.value,
+                    mCachedBrightnessInfo.hbmMode.value,
+                    mCachedBrightnessInfo.hbmTransitionPoint.value,
+                    mCachedBrightnessInfo.brightnessMaxReason.value);
+        }
+    }
+
+    private boolean saveBrightnessInfo(float brightness) {
+        return saveBrightnessInfo(brightness, brightness);
+    }
+
+    private boolean saveBrightnessInfo(float brightness, float adjustedBrightness) {
+        synchronized (mCachedBrightnessInfo) {
+            final float minBrightness = Math.min(mHbmController.getCurrentBrightnessMin(),
+                    mBrightnessThrottler.getBrightnessCap());
+            final float maxBrightness = Math.min(mHbmController.getCurrentBrightnessMax(),
+                    mBrightnessThrottler.getBrightnessCap());
+            boolean changed = false;
+
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightness,
+                            brightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.adjustedBrightness,
+                            adjustedBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMin,
+                            minBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.brightnessMax,
+                            maxBrightness);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.hbmMode,
+                            mHbmController.getHighBrightnessMode());
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetFloat(mCachedBrightnessInfo.hbmTransitionPoint,
+                            mHbmController.getTransitionPoint());
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
+                            mBrightnessThrottler.getBrightnessMaxReason());
+
+            return changed;
+        }
+    }
+
+    void postBrightnessChangeRunnable() {
+        mHandler.post(mOnBrightnessChangeRunnable);
+    }
+
+    private HighBrightnessModeController createHbmControllerLocked() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+        final IBinder displayToken =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
+        final String displayUniqueId =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+        final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+                ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
+                displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
+                new HighBrightnessModeController.HdrBrightnessDeviceConfig() {
+                    @Override
+                    public float getHdrBrightnessFromSdr(float sdrBrightness) {
+                        return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness);
+                    }
+                },
+                () -> {
+                    sendUpdatePowerState();
+                    postBrightnessChangeRunnable();
+                    // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
+                    if (mAutomaticBrightnessController != null) {
+                        mAutomaticBrightnessController.update();
+                    }
+                }, mContext);
+    }
+
+    private BrightnessThrottler createBrightnessThrottlerLocked() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
+        final DisplayDeviceConfig.BrightnessThrottlingData data =
+                ddConfig != null ? ddConfig.getBrightnessThrottlingData() : null;
+        return new BrightnessThrottler(mHandler, data,
+                () -> {
+                    sendUpdatePowerState();
+                    postBrightnessChangeRunnable();
+                }, mUniqueDisplayId);
+    }
+
+    private void blockScreenOn() {
+        if (mPendingScreenOnUnblocker == null) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+            mPendingScreenOnUnblocker = new ScreenOnUnblocker();
+            mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
+            Slog.i(mTag, "Blocking screen on until initial contents have been drawn.");
+        }
+    }
+
+    private void unblockScreenOn() {
+        if (mPendingScreenOnUnblocker != null) {
+            mPendingScreenOnUnblocker = null;
+            long delay = SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime;
+            Slog.i(mTag, "Unblocked screen on after " + delay + " ms");
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
+        }
+    }
+
+    private void blockScreenOff() {
+        if (mPendingScreenOffUnblocker == null) {
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+            mPendingScreenOffUnblocker = new ScreenOffUnblocker();
+            mScreenOffBlockStartRealTime = SystemClock.elapsedRealtime();
+            Slog.i(mTag, "Blocking screen off");
+        }
+    }
+
+    private void unblockScreenOff() {
+        if (mPendingScreenOffUnblocker != null) {
+            mPendingScreenOffUnblocker = null;
+            long delay = SystemClock.elapsedRealtime() - mScreenOffBlockStartRealTime;
+            Slog.i(mTag, "Unblocked screen off after " + delay + " ms");
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, SCREEN_OFF_BLOCKED_TRACE_NAME, 0);
+        }
+    }
+
+    private boolean setScreenState(int state) {
+        return setScreenState(state, false /*reportOnly*/);
+    }
+
+    private boolean setScreenState(int state, boolean reportOnly) {
+        final boolean isOff = (state == Display.STATE_OFF);
+
+        if (mPowerState.getScreenState() != state
+                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+            // If we are trying to turn screen off, give policy a chance to do something before we
+            // actually turn the screen off.
+            if (isOff && !mScreenOffBecauseOfProximity) {
+                if (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_ON
+                        || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED) {
+                    setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_OFF);
+                    blockScreenOff();
+                    mWindowManagerPolicy.screenTurningOff(mDisplayId, mPendingScreenOffUnblocker);
+                    unblockScreenOff();
+                } else if (mPendingScreenOffUnblocker != null) {
+                    // Abort doing the state change until screen off is unblocked.
+                    return false;
+                }
+            }
+
+            if (!reportOnly && mPowerState.getScreenState() != state) {
+                Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state);
+                // TODO(b/153319140) remove when we can get this from the above trace invocation
+                SystemProperties.set("debug.tracing.screen_state", String.valueOf(state));
+                mPowerState.setScreenState(state);
+                // Tell battery stats about the transition.
+                noteScreenState(state);
+            }
+        }
+
+        // Tell the window manager policy when the screen is turned off or on unless it's due
+        // to the proximity sensor.  We temporarily block turning the screen on until the
+        // window manager is ready by leaving a black surface covering the screen.
+        // This surface is essentially the final state of the color fade animation and
+        // it is only removed once the window manager tells us that the activity has
+        // finished drawing underneath.
+        if (isOff && mReportedScreenStateToPolicy != REPORTED_TO_POLICY_SCREEN_OFF
+                && !mScreenOffBecauseOfProximity) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+            unblockScreenOn();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+        } else if (!isOff
+                && mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_TURNING_OFF) {
+
+            // We told policy already that screen was turning off, but now we changed our minds.
+            // Complete the full state transition on -> turningOff -> off.
+            unblockScreenOff();
+            mWindowManagerPolicy.screenTurnedOff(mDisplayId);
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_OFF);
+        }
+        if (!isOff
+                && (mReportedScreenStateToPolicy == REPORTED_TO_POLICY_SCREEN_OFF
+                || mReportedScreenStateToPolicy == REPORTED_TO_POLICY_UNREPORTED)) {
+            setReportedScreenState(REPORTED_TO_POLICY_SCREEN_TURNING_ON);
+            if (mPowerState.getColorFadeLevel() == 0.0f) {
+                blockScreenOn();
+            } else {
+                unblockScreenOn();
+            }
+            mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker);
+        }
+
+        // Return true if the screen isn't blocked.
+        return mPendingScreenOnUnblocker == null;
+    }
+
+    private void setReportedScreenState(int state) {
+        Trace.traceCounter(Trace.TRACE_TAG_POWER, "ReportedScreenStateToPolicy", state);
+        mReportedScreenStateToPolicy = state;
+    }
+
+    private void loadAmbientLightSensor() {
+        DisplayDeviceConfig.SensorData lightSensor = mDisplayDeviceConfig.getAmbientLightSensor();
+        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+                ? Sensor.TYPE_LIGHT : SensorUtils.NO_FALLBACK;
+        mLightSensor = SensorUtils.findSensor(mSensorManager, lightSensor.type, lightSensor.name,
+                fallbackType);
+    }
+
+    private void loadProximitySensor() {
+        if (DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+            return;
+        }
+        final DisplayDeviceConfig.SensorData proxSensor =
+                mDisplayDeviceConfig.getProximitySensor();
+        final int fallbackType = mDisplayId == Display.DEFAULT_DISPLAY
+                ? Sensor.TYPE_PROXIMITY : SensorUtils.NO_FALLBACK;
+        mProximitySensor = SensorUtils.findSensor(mSensorManager, proxSensor.type, proxSensor.name,
+                fallbackType);
+        if (mProximitySensor != null) {
+            mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+                    TYPICAL_PROXIMITY_THRESHOLD);
+        }
+    }
+
+    private float clampScreenBrightnessForVr(float value) {
+        return MathUtils.constrain(
+                value, mScreenBrightnessForVrRangeMinimum,
+                mScreenBrightnessForVrRangeMaximum);
+    }
+
+    private float clampScreenBrightness(float value) {
+        if (Float.isNaN(value)) {
+            value = PowerManager.BRIGHTNESS_MIN;
+        }
+        return MathUtils.constrain(value,
+                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
+    }
+
+    // Checks whether the brightness is within the valid brightness range, not including off.
+    private boolean isValidBrightnessValue(float brightness) {
+        return brightness >= PowerManager.BRIGHTNESS_MIN
+                && brightness <= PowerManager.BRIGHTNESS_MAX;
+    }
+
+    private void animateScreenBrightness(float target, float sdrTarget, float rate) {
+        if (DEBUG) {
+            Slog.d(mTag, "Animating brightness: target=" + target + ", sdrTarget=" + sdrTarget
+                    + ", rate=" + rate);
+        }
+        if (mScreenBrightnessRampAnimator.animateTo(target, sdrTarget, rate)) {
+            Trace.traceCounter(Trace.TRACE_TAG_POWER, "TargetScreenBrightness", (int) target);
+            // TODO(b/153319140) remove when we can get this from the above trace invocation
+            SystemProperties.set("debug.tracing.screen_brightness", String.valueOf(target));
+            noteScreenBrightness(target);
+        }
+    }
+
+    private void animateScreenStateChange(int target, boolean performScreenOffTransition) {
+        // If there is already an animation in progress, don't interfere with it.
+        if (mColorFadeEnabled
+                && (mColorFadeOnAnimator.isStarted() || mColorFadeOffAnimator.isStarted())) {
+            if (target != Display.STATE_ON) {
+                return;
+            }
+            // If display state changed to on, proceed and stop the color fade and turn screen on.
+            mPendingScreenOff = false;
+        }
+
+        if (mDisplayBlanksAfterDozeConfig
+                && Display.isDozeState(mPowerState.getScreenState())
+                && !Display.isDozeState(target)) {
+            // Skip the screen off animation and add a black surface to hide the
+            // contents of the screen.
+            mPowerState.prepareColorFade(mContext,
+                    mColorFadeFadesConfig ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP);
+            if (mColorFadeOffAnimator != null) {
+                mColorFadeOffAnimator.end();
+            }
+            // Some display hardware will blank itself on the transition between doze and non-doze
+            // but still on display states. In this case we want to report to policy that the
+            // display has turned off so it can prepare the appropriate power on animation, but we
+            // don't want to actually transition to the fully off state since that takes
+            // significantly longer to transition from.
+            setScreenState(Display.STATE_OFF, target != Display.STATE_OFF /*reportOnly*/);
+        }
+
+        // If we were in the process of turning off the screen but didn't quite
+        // finish.  Then finish up now to prevent a jarring transition back
+        // to screen on if we skipped blocking screen on as usual.
+        if (mPendingScreenOff && target != Display.STATE_OFF) {
+            setScreenState(Display.STATE_OFF);
+            mPendingScreenOff = false;
+            mPowerState.dismissColorFadeResources();
+        }
+
+        if (target == Display.STATE_ON) {
+            // Want screen on.  The contents of the screen may not yet
+            // be visible if the color fade has not been dismissed because
+            // its last frame of animation is solid black.
+            if (!setScreenState(Display.STATE_ON)) {
+                return; // screen on blocked
+            }
+            if (USE_COLOR_FADE_ON_ANIMATION && mColorFadeEnabled && mPowerRequest.isBrightOrDim()) {
+                // Perform screen on animation.
+                if (mPowerState.getColorFadeLevel() == 1.0f) {
+                    mPowerState.dismissColorFade();
+                } else if (mPowerState.prepareColorFade(mContext,
+                        mColorFadeFadesConfig
+                                ? ColorFade.MODE_FADE : ColorFade.MODE_WARM_UP)) {
+                    mColorFadeOnAnimator.start();
+                } else {
+                    mColorFadeOnAnimator.end();
+                }
+            } else {
+                // Skip screen on animation.
+                mPowerState.setColorFadeLevel(1.0f);
+                mPowerState.dismissColorFade();
+            }
+        } else if (target == Display.STATE_VR) {
+            // Wait for brightness animation to complete beforehand when entering VR
+            // from screen on to prevent a perceptible jump because brightness may operate
+            // differently when the display is configured for dozing.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() == Display.STATE_ON) {
+                return;
+            }
+
+            // Set screen state.
+            if (!setScreenState(Display.STATE_VR)) {
+                return; // screen on blocked
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_DOZE) {
+            // Want screen dozing.
+            // Wait for brightness animation to complete beforehand when entering doze
+            // from screen on to prevent a perceptible jump because brightness may operate
+            // differently when the display is configured for dozing.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() == Display.STATE_ON) {
+                return;
+            }
+
+            // Set screen state.
+            if (!setScreenState(Display.STATE_DOZE)) {
+                return; // screen on blocked
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_DOZE_SUSPEND) {
+            // Want screen dozing and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to doze until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_DOZE_SUSPEND) {
+                if (!setScreenState(Display.STATE_DOZE)) {
+                    return; // screen on blocked
+                }
+                setScreenState(Display.STATE_DOZE_SUSPEND); // already on so can't block
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else if (target == Display.STATE_ON_SUSPEND) {
+            // Want screen full-power and suspended.
+            // Wait for brightness animation to complete beforehand unless already
+            // suspended because we may not be able to change it after suspension.
+            if (mScreenBrightnessRampAnimator.isAnimating()
+                    && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                return;
+            }
+
+            // If not already suspending, temporarily set the state to on until the
+            // screen on is unblocked, then suspend.
+            if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+                if (!setScreenState(Display.STATE_ON)) {
+                    return;
+                }
+                setScreenState(Display.STATE_ON_SUSPEND);
+            }
+
+            // Dismiss the black surface without fanfare.
+            mPowerState.setColorFadeLevel(1.0f);
+            mPowerState.dismissColorFade();
+        } else {
+            // Want screen off.
+            mPendingScreenOff = true;
+            if (!mColorFadeEnabled) {
+                mPowerState.setColorFadeLevel(0.0f);
+            }
+
+            if (mPowerState.getColorFadeLevel() == 0.0f) {
+                // Turn the screen off.
+                // A black surface is already hiding the contents of the screen.
+                setScreenState(Display.STATE_OFF);
+                mPendingScreenOff = false;
+                mPowerState.dismissColorFadeResources();
+            } else if (performScreenOffTransition
+                    && mPowerState.prepareColorFade(mContext,
+                    mColorFadeFadesConfig
+                            ? ColorFade.MODE_FADE : ColorFade.MODE_COOL_DOWN)
+                    && mPowerState.getScreenState() != Display.STATE_OFF) {
+                // Perform the screen off animation.
+                mColorFadeOffAnimator.start();
+            } else {
+                // Skip the screen off animation and add a black surface to hide the
+                // contents of the screen.
+                mColorFadeOffAnimator.end();
+            }
+        }
+    }
+
+    private final Runnable mCleanListener = this::sendUpdatePowerState;
+
+    private void setProximitySensorEnabled(boolean enable) {
+        if (enable) {
+            if (!mProximitySensorEnabled) {
+                // Register the listener.
+                // Proximity sensor state already cleared initially.
+                mProximitySensorEnabled = true;
+                mIgnoreProximityUntilChanged = false;
+                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+            }
+        } else {
+            if (mProximitySensorEnabled) {
+                // Unregister the listener.
+                // Clear the proximity sensor state for next time.
+                mProximitySensorEnabled = false;
+                mProximity = PROXIMITY_UNKNOWN;
+                mIgnoreProximityUntilChanged = false;
+                mPendingProximity = PROXIMITY_UNKNOWN;
+                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mSensorManager.unregisterListener(mProximitySensorListener);
+                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+            }
+        }
+    }
+
+    private void handleProximitySensorEvent(long time, boolean positive) {
+        if (mProximitySensorEnabled) {
+            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+                return; // no change
+            }
+            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+                return; // no change
+            }
+
+            // Only accept a proximity sensor reading if it remains
+            // stable for the entire debounce delay.  We hold a wake lock while
+            // debouncing the sensor.
+            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+            if (positive) {
+                mPendingProximity = PROXIMITY_POSITIVE;
+                setPendingProximityDebounceTime(
+                        time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
+            } else {
+                mPendingProximity = PROXIMITY_NEGATIVE;
+                setPendingProximityDebounceTime(
+                        time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
+            }
+
+            // Debounce the new sensor reading.
+            debounceProximitySensor();
+        }
+    }
+
+    private void debounceProximitySensor() {
+        if (mProximitySensorEnabled
+                && mPendingProximity != PROXIMITY_UNKNOWN
+                && mPendingProximityDebounceTime >= 0) {
+            final long now = mClock.uptimeMillis();
+            if (mPendingProximityDebounceTime <= now) {
+                if (mProximity != mPendingProximity) {
+                    // if the status of the sensor changed, stop ignoring.
+                    mIgnoreProximityUntilChanged = false;
+                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+                }
+                // Sensor reading accepted.  Apply the change then release the wake lock.
+                mProximity = mPendingProximity;
+                updatePowerState();
+                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+            } else {
+                // Need to wait a little longer.
+                // Debounce again later.  We continue holding a wake lock while waiting.
+                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+            }
+        }
+    }
+
+    private void clearPendingProximityDebounceTime() {
+        if (mPendingProximityDebounceTime >= 0) {
+            mPendingProximityDebounceTime = -1;
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxDebounce);
+        }
+    }
+
+    private void setPendingProximityDebounceTime(long debounceTime) {
+        if (mPendingProximityDebounceTime < 0) {
+            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxDebounce);
+        }
+        mPendingProximityDebounceTime = debounceTime;
+    }
+
+    private void sendOnStateChangedWithWakelock() {
+        if (!mOnStateChangedPending) {
+            mOnStateChangedPending = true;
+            mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+            mHandler.post(mOnStateChangedRunnable);
+        }
+    }
+
+    private void logDisplayPolicyChanged(int newPolicy) {
+        LogMaker log = new LogMaker(MetricsEvent.DISPLAY_POLICY);
+        log.setType(MetricsEvent.TYPE_UPDATE);
+        log.setSubtype(newPolicy);
+        MetricsLogger.action(log);
+    }
+
+    private void handleSettingsChange(boolean userSwitch) {
+        mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+        mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+        if (userSwitch) {
+            // Don't treat user switches as user initiated change.
+            setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+            updateAutoBrightnessAdjustment();
+            if (mAutomaticBrightnessController != null) {
+                mAutomaticBrightnessController.resetShortTermModel();
+            }
+        }
+        // We don't bother with a pending variable for VR screen brightness since we just
+        // immediately adapt to it.
+        mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+        sendUpdatePowerState();
+    }
+
+    private float getAutoBrightnessAdjustmentSetting() {
+        final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+        return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
+    }
+
+    @Override
+    public float getScreenBrightnessSetting() {
+        float brightness = mBrightnessSetting.getBrightness();
+        if (Float.isNaN(brightness)) {
+            brightness = mScreenBrightnessDefault;
+        }
+        return clampAbsoluteBrightness(brightness);
+    }
+
+    private float getScreenBrightnessForVrSetting() {
+        final float brightnessFloat = Settings.System.getFloatForUser(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT, mScreenBrightnessForVrDefault,
+                UserHandle.USER_CURRENT);
+        return clampScreenBrightnessForVr(brightnessFloat);
+    }
+
+    @Override
+    public void setBrightness(float brightnessValue) {
+        // Update the setting, which will eventually call back into DPC to have us actually update
+        // the display with the new value.
+        mBrightnessSetting.setBrightness(brightnessValue);
+    }
+
+    private void updateScreenBrightnessSetting(float brightnessValue) {
+        if (!isValidBrightnessValue(brightnessValue)
+                || brightnessValue == mCurrentScreenBrightnessSetting) {
+            return;
+        }
+        setCurrentScreenBrightness(brightnessValue);
+        mBrightnessSetting.setBrightness(brightnessValue);
+    }
+
+    private void setCurrentScreenBrightness(float brightnessValue) {
+        if (brightnessValue != mCurrentScreenBrightnessSetting) {
+            mCurrentScreenBrightnessSetting = brightnessValue;
+            postBrightnessChangeRunnable();
+        }
+    }
+
+    private void putAutoBrightnessAdjustmentSetting(float adjustment) {
+        if (mDisplayId == Display.DEFAULT_DISPLAY) {
+            mAutoBrightnessAdjustment = adjustment;
+            Settings.System.putFloatForUser(mContext.getContentResolver(),
+                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+                    UserHandle.USER_CURRENT);
+        }
+    }
+
+    private boolean updateAutoBrightnessAdjustment() {
+        if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+            return false;
+        }
+        if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+            mPendingAutoBrightnessAdjustment = Float.NaN;
+            return false;
+        }
+        mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+        mPendingAutoBrightnessAdjustment = Float.NaN;
+        mTemporaryAutoBrightnessAdjustment = Float.NaN;
+        return true;
+    }
+
+    // We want to return true if the user has set the screen brightness.
+    // RBC on, off, and intensity changes will return false.
+    // Slider interactions whilst in RBC will return true, just as when in non-rbc.
+    private boolean updateUserSetScreenBrightness() {
+        if ((Float.isNaN(mPendingScreenBrightnessSetting)
+                || mPendingScreenBrightnessSetting < 0.0f)) {
+            return false;
+        }
+        if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
+            mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+            return false;
+        }
+        setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
+        mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
+        mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        return true;
+    }
+
+    private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
+            boolean hadUserDataPoint) {
+        final float brightnessInNits = convertToNits(brightness);
+        if (mPowerRequest.useAutoBrightness && brightnessInNits >= 0.0f
+                && mAutomaticBrightnessController != null) {
+            // We only want to track changes on devices that can actually map the display backlight
+            // values into a physical brightness unit since the value provided by the API is in
+            // nits and not using the arbitrary backlight units.
+            final float powerFactor = mPowerRequest.lowPowerMode
+                    ? mPowerRequest.screenLowPowerBrightnessFactor
+                    : 1.0f;
+            mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
+                    powerFactor, hadUserDataPoint,
+                    mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+        }
+    }
+
+    private float convertToNits(float brightness) {
+        if (mAutomaticBrightnessController == null) {
+            return -1f;
+        }
+        return mAutomaticBrightnessController.convertToNits(brightness);
+    }
+
+    @GuardedBy("mLock")
+    private void updatePendingProximityRequestsLocked() {
+        mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+        mPendingWaitForNegativeProximityLocked = false;
+
+        if (mIgnoreProximityUntilChanged) {
+            // Also, lets stop waiting for negative proximity if we're ignoring it.
+            mWaitingForNegativeProximity = false;
+        }
+    }
+
+    private void ignoreProximitySensorUntilChangedInternal() {
+        if (!mIgnoreProximityUntilChanged
+                && mProximity == PROXIMITY_POSITIVE) {
+            // Only ignore if it is still reporting positive (near)
+            mIgnoreProximityUntilChanged = true;
+            Slog.i(mTag, "Ignoring proximity");
+            updatePowerState();
+        }
+    }
+
+    private final Runnable mOnStateChangedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mOnStateChangedPending = false;
+            mCallbacks.onStateChanged();
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+        }
+    };
+
+    private void sendOnProximityPositiveWithWakelock() {
+        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
+        mHandler.post(mOnProximityPositiveRunnable);
+        mOnProximityPositiveMessages++;
+    }
+
+    private final Runnable mOnProximityPositiveRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mOnProximityPositiveMessages--;
+            mCallbacks.onProximityPositive();
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+        }
+    };
+
+    private void sendOnProximityNegativeWithWakelock() {
+        mOnProximityNegativeMessages++;
+        mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
+        mHandler.post(mOnProximityNegativeRunnable);
+    }
+
+    private final Runnable mOnProximityNegativeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mOnProximityNegativeMessages--;
+            mCallbacks.onProximityNegative();
+            mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+        }
+    };
+
+    @Override
+    public void dump(final PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Display Power Controller:");
+            pw.println("  mDisplayId=" + mDisplayId);
+            pw.println("  mLightSensor=" + mLightSensor);
+
+            pw.println();
+            pw.println("Display Power Controller Locked State:");
+            pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
+            pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
+            pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
+            pw.println("  mPendingWaitForNegativeProximityLocked="
+                    + mPendingWaitForNegativeProximityLocked);
+            pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
+        }
+
+        pw.println();
+        pw.println("Display Power Controller Configuration:");
+        pw.println("  mScreenBrightnessRangeDefault=" + mScreenBrightnessDefault);
+        pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
+        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+        pw.println("  mScreenBrightnessForVrRangeMinimum=" + mScreenBrightnessForVrRangeMinimum);
+        pw.println("  mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
+        pw.println("  mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
+        pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
+        pw.println("  mAllowAutoBrightnessWhileDozingConfig="
+                + mAllowAutoBrightnessWhileDozingConfig);
+        pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
+        pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
+        pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
+        synchronized (mCachedBrightnessInfo) {
+            pw.println("  mCachedBrightnessInfo.brightness="
+                    + mCachedBrightnessInfo.brightness.value);
+            pw.println("  mCachedBrightnessInfo.adjustedBrightness="
+                    + mCachedBrightnessInfo.adjustedBrightness.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMin="
+                    + mCachedBrightnessInfo.brightnessMin.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMax="
+                    + mCachedBrightnessInfo.brightnessMax.value);
+            pw.println("  mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
+            pw.println("  mCachedBrightnessInfo.hbmTransitionPoint="
+                    + mCachedBrightnessInfo.hbmTransitionPoint.value);
+            pw.println("  mCachedBrightnessInfo.brightnessMaxReason ="
+                    + mCachedBrightnessInfo.brightnessMaxReason.value);
+        }
+        pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
+        pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+
+        mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
+    }
+
+    private void dumpLocal(PrintWriter pw) {
+        pw.println();
+        pw.println("Display Power Controller Thread State:");
+        pw.println("  mPowerRequest=" + mPowerRequest);
+        pw.println("  mUnfinishedBusiness=" + mUnfinishedBusiness);
+        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+        pw.println("  mProximitySensor=" + mProximitySensor);
+        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
+        pw.println("  mProximityThreshold=" + mProximityThreshold);
+        pw.println("  mProximity=" + proximityToString(mProximity));
+        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
+        pw.println("  mPendingProximityDebounceTime="
+                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+        pw.println("  mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
+        pw.println("  mPendingScreenBrightnessSetting="
+                + mPendingScreenBrightnessSetting);
+        pw.println("  mTemporaryScreenBrightness=" + mTemporaryScreenBrightness);
+        pw.println("  mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+        pw.println("  mBrightnessReason=" + mBrightnessReason);
+        pw.println("  mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+        pw.println("  mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+        pw.println("  mScreenBrightnessForVrFloat=" + mScreenBrightnessForVr);
+        pw.println("  mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+        pw.println("  mAppliedDimming=" + mAppliedDimming);
+        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
+        pw.println("  mAppliedThrottling=" + mAppliedThrottling);
+        pw.println("  mAppliedScreenBrightnessOverride=" + mAppliedScreenBrightnessOverride);
+        pw.println("  mAppliedTemporaryBrightness=" + mAppliedTemporaryBrightness);
+        pw.println("  mAppliedTemporaryAutoBrightnessAdjustment="
+                + mAppliedTemporaryAutoBrightnessAdjustment);
+        pw.println("  mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
+        pw.println("  mDozing=" + mDozing);
+        pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
+        pw.println("  mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
+        pw.println("  mScreenOffBlockStartRealTime=" + mScreenOffBlockStartRealTime);
+        pw.println("  mPendingScreenOnUnblocker=" + mPendingScreenOnUnblocker);
+        pw.println("  mPendingScreenOffUnblocker=" + mPendingScreenOffUnblocker);
+        pw.println("  mPendingScreenOff=" + mPendingScreenOff);
+        pw.println("  mReportedToPolicy="
+                + reportedToPolicyToString(mReportedScreenStateToPolicy));
+        pw.println("  mIsRbcActive=" + mIsRbcActive);
+        pw.println("  mOnStateChangePending=" + mOnStateChangedPending);
+        pw.println("  mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
+        pw.println("  mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
+
+        if (mScreenBrightnessRampAnimator != null) {
+            pw.println("  mScreenBrightnessRampAnimator.isAnimating()="
+                    + mScreenBrightnessRampAnimator.isAnimating());
+        }
+
+        if (mColorFadeOnAnimator != null) {
+            pw.println("  mColorFadeOnAnimator.isStarted()="
+                    + mColorFadeOnAnimator.isStarted());
+        }
+        if (mColorFadeOffAnimator != null) {
+            pw.println("  mColorFadeOffAnimator.isStarted()="
+                    + mColorFadeOffAnimator.isStarted());
+        }
+
+        if (mPowerState != null) {
+            mPowerState.dump(pw);
+        }
+
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.dump(pw);
+            dumpBrightnessEvents(pw);
+        }
+
+        if (mHbmController != null) {
+            mHbmController.dump(pw);
+        }
+
+        if (mBrightnessThrottler != null) {
+            mBrightnessThrottler.dump(pw);
+        }
+
+        pw.println();
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.dump(pw);
+            mDisplayWhiteBalanceSettings.dump(pw);
+        }
+    }
+
+    private static String proximityToString(int state) {
+        switch (state) {
+            case PROXIMITY_UNKNOWN:
+                return "Unknown";
+            case PROXIMITY_NEGATIVE:
+                return "Negative";
+            case PROXIMITY_POSITIVE:
+                return "Positive";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private static String reportedToPolicyToString(int state) {
+        switch (state) {
+            case REPORTED_TO_POLICY_SCREEN_OFF:
+                return "REPORTED_TO_POLICY_SCREEN_OFF";
+            case REPORTED_TO_POLICY_SCREEN_TURNING_ON:
+                return "REPORTED_TO_POLICY_SCREEN_TURNING_ON";
+            case REPORTED_TO_POLICY_SCREEN_ON:
+                return "REPORTED_TO_POLICY_SCREEN_ON";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private static String skipRampStateToString(int state) {
+        switch (state) {
+            case RAMP_STATE_SKIP_NONE:
+                return "RAMP_STATE_SKIP_NONE";
+            case RAMP_STATE_SKIP_INITIAL:
+                return "RAMP_STATE_SKIP_INITIAL";
+            case RAMP_STATE_SKIP_AUTOBRIGHT:
+                return "RAMP_STATE_SKIP_AUTOBRIGHT";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private void dumpBrightnessEvents(PrintWriter pw) {
+        int size = mBrightnessEventRingBuffer.size();
+        if (size < 1) {
+            pw.println("No Automatic Brightness Adjustments");
+            return;
+        }
+
+        pw.println("Automatic Brightness Adjustments Last " + size + " Events: ");
+        BrightnessEvent[] eventArray = mBrightnessEventRingBuffer.toArray();
+        for (int i = 0; i < mBrightnessEventRingBuffer.size(); i++) {
+            pw.println("  " + eventArray[i].toString());
+        }
+    }
+
+    private static float clampAbsoluteBrightness(float value) {
+        return MathUtils.constrain(value, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX);
+    }
+
+    private static float clampAutoBrightnessAdjustment(float value) {
+        return MathUtils.constrain(value, -1.0f, 1.0f);
+    }
+
+    private void noteScreenState(int screenState) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(multi-display): make this multi-display
+                mBatteryStats.noteScreenState(screenState);
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
+    private void noteScreenBrightness(float brightness) {
+        if (mBatteryStats != null) {
+            try {
+                // TODO(brightnessfloat): change BatteryStats to use float
+                mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
+                        brightness));
+            } catch (RemoteException e) {
+                // same process
+            }
+        }
+    }
+
+    private void reportStats(float brightness) {
+        if (mLastStatsBrightness == brightness) {
+            return;
+        }
+
+        float hbmTransitionPoint = PowerManager.BRIGHTNESS_MAX;
+        synchronized (mCachedBrightnessInfo) {
+            if (mCachedBrightnessInfo.hbmTransitionPoint == null) {
+                return;
+            }
+            hbmTransitionPoint = mCachedBrightnessInfo.hbmTransitionPoint.value;
+        }
+
+        final boolean aboveTransition = brightness > hbmTransitionPoint;
+        final boolean oldAboveTransition = mLastStatsBrightness > hbmTransitionPoint;
+
+        if (aboveTransition || oldAboveTransition) {
+            mLastStatsBrightness = brightness;
+            mHandler.removeMessages(MSG_STATSD_HBM_BRIGHTNESS);
+            if (aboveTransition != oldAboveTransition) {
+                // report immediately
+                logHbmBrightnessStats(brightness, mDisplayStatsId);
+            } else {
+                // delay for rate limiting
+                Message msg = mHandler.obtainMessage();
+                msg.what = MSG_STATSD_HBM_BRIGHTNESS;
+                msg.arg1 = Float.floatToIntBits(brightness);
+                msg.arg2 = mDisplayStatsId;
+                mHandler.sendMessageDelayed(msg, BRIGHTNESS_CHANGE_STATSD_REPORT_INTERVAL_MS);
+            }
+        }
+    }
+
+    private void logHbmBrightnessStats(float brightness, int displayStatsId) {
+        synchronized (mHandler) {
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.DISPLAY_HBM_BRIGHTNESS_CHANGED, displayStatsId, brightness);
+        }
+    }
+
+    private void logManualBrightnessEvent(BrightnessEvent event) {
+        float appliedLowPowerMode = event.isLowPowerModeSet() ? event.getPowerFactor() : -1f;
+        int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
+        float appliedHbmMaxNits =
+                event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+                ? -1f : convertToNits(event.getHbmMax());
+        // thermalCapNits set to -1 if not currently capping max brightness
+        float appliedThermalCapNits =
+                event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
+                ? -1f : convertToNits(event.getThermalMax());
+
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
+                convertToNits(event.getInitialBrightness()),
+                convertToNits(event.getBrightness()),
+                event.getSlowAmbientLux(),
+                event.getPhysicalDisplayId(),
+                event.isShortTermModelActive(),
+                appliedLowPowerMode,
+                appliedRbcStrength,
+                appliedHbmMaxNits,
+                appliedThermalCapNits,
+                event.isAutomaticBrightnessEnabled(),
+                FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED__REASON__REASON_MANUAL);
+    }
+
+    private final class DisplayControllerHandler extends Handler {
+        DisplayControllerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_POWER_STATE:
+                    updatePowerState();
+                    break;
+
+                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+                    debounceProximitySensor();
+                    break;
+
+                case MSG_SCREEN_ON_UNBLOCKED:
+                    if (mPendingScreenOnUnblocker == msg.obj) {
+                        unblockScreenOn();
+                        updatePowerState();
+                    }
+                    break;
+                case MSG_SCREEN_OFF_UNBLOCKED:
+                    if (mPendingScreenOffUnblocker == msg.obj) {
+                        unblockScreenOff();
+                        updatePowerState();
+                    }
+                    break;
+                case MSG_CONFIGURE_BRIGHTNESS:
+                    mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
+                    updatePowerState();
+                    break;
+
+                case MSG_SET_TEMPORARY_BRIGHTNESS:
+                    // TODO: Should we have a a timeout for the temporary brightness?
+                    mTemporaryScreenBrightness = Float.intBitsToFloat(msg.arg1);
+                    updatePowerState();
+                    break;
+
+                case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
+                    mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+                    updatePowerState();
+                    break;
+
+                case MSG_IGNORE_PROXIMITY:
+                    ignoreProximitySensorUntilChangedInternal();
+                    break;
+
+                case MSG_STOP:
+                    cleanupHandlerThreadAfterStop();
+                    break;
+
+                case MSG_UPDATE_BRIGHTNESS:
+                    if (mStopped) {
+                        return;
+                    }
+                    handleSettingsChange(false /*userSwitch*/);
+                    break;
+
+                case MSG_UPDATE_RBC:
+                    handleRbcChanged();
+                    break;
+
+                case MSG_BRIGHTNESS_RAMP_DONE:
+                    if (mPowerState != null) {
+                        final float brightness = mPowerState.getScreenBrightness();
+                        reportStats(brightness);
+                    }
+                    break;
+
+                case MSG_STATSD_HBM_BRIGHTNESS:
+                    logHbmBrightnessStats(Float.intBitsToFloat(msg.arg1), msg.arg2);
+                    break;
+            }
+        }
+    }
+
+    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mProximitySensorEnabled) {
+                final long time = mClock.uptimeMillis();
+                final float distance = event.values[0];
+                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+                handleProximitySensorEvent(time, positive);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            handleSettingsChange(false /* userSwitch */);
+        }
+    }
+
+    private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
+        @Override
+        public void onScreenOn() {
+            Message msg = mHandler.obtainMessage(MSG_SCREEN_ON_UNBLOCKED, this);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private final class ScreenOffUnblocker implements WindowManagerPolicy.ScreenOffListener {
+        @Override
+        public void onScreenOff() {
+            Message msg = mHandler.obtainMessage(MSG_SCREEN_OFF_UNBLOCKED, this);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    @Override
+    public void setAutoBrightnessLoggingEnabled(boolean enabled) {
+        if (mAutomaticBrightnessController != null) {
+            mAutomaticBrightnessController.setLoggingEnabled(enabled);
+        }
+    }
+
+    @Override // DisplayWhiteBalanceController.Callbacks
+    public void updateWhiteBalance() {
+        sendUpdatePowerState();
+    }
+
+    @Override
+    public void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setLoggingEnabled(enabled);
+            mDisplayWhiteBalanceSettings.setLoggingEnabled(enabled);
+        }
+    }
+
+    @Override
+    public void setAmbientColorTemperatureOverride(float cct) {
+        if (mDisplayWhiteBalanceController != null) {
+            mDisplayWhiteBalanceController.setAmbientColorTemperatureOverride(cct);
+            // The ambient color temperature override is only applied when the ambient color
+            // temperature changes or is updated, so it doesn't necessarily change the screen color
+            // temperature immediately. So, let's make it!
+            sendUpdatePowerState();
+        }
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerUnfinishedBusinessId(int displayId) {
+        return "[" + displayId + "]unfinished business";
+    }
+
+    String getSuspendBlockerOnStateChangedId(int displayId) {
+        return "[" + displayId + "]on state changed";
+    }
+
+    String getSuspendBlockerProxPositiveId(int displayId) {
+        return "[" + displayId + "]prox positive";
+    }
+
+    String getSuspendBlockerProxNegativeId(int displayId) {
+        return "[" + displayId + "]prox negative";
+    }
+
+    @VisibleForTesting
+    String getSuspendBlockerProxDebounceId(int displayId) {
+        return "[" + displayId + "]prox debounce";
+    }
+
+    /** Functional interface for providing time. */
+    @VisibleForTesting
+    interface Clock {
+        /**
+         * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+         */
+        long uptimeMillis();
+    }
+
+    @VisibleForTesting
+    static class Injector {
+        Clock getClock() {
+            return SystemClock::uptimeMillis;
+        }
+
+        DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                int displayId, int displayState) {
+            return new DisplayPowerState(blanker, colorFade, displayId, displayState);
+        }
+
+        DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                FloatProperty<DisplayPowerState> firstProperty,
+                FloatProperty<DisplayPowerState> secondProperty) {
+            return new DualRampAnimator(dps, firstProperty, secondProperty);
+        }
+    }
+
+    static class CachedBrightnessInfo {
+        public MutableFloat brightness = new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat adjustedBrightness =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat brightnessMin =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableFloat brightnessMax =
+                new MutableFloat(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+        public MutableInt hbmMode = new MutableInt(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+        public MutableFloat hbmTransitionPoint =
+                new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
+        public MutableInt brightnessMaxReason =
+                new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+
+        public boolean checkAndSetFloat(MutableFloat mf, float f) {
+            if (mf.value != f) {
+                mf.value = f;
+                return true;
+            }
+            return false;
+        }
+
+        public boolean checkAndSetInt(MutableInt mi, int i) {
+            if (mi.value != i) {
+                mi.value = i;
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index 3413489..abf8fe3 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -36,8 +36,7 @@
     private final float mMinBrightening;
 
     /**
-     * Creates a {@code HysteresisLevels} object with the given equal-length
-     * integer arrays.
+     * Creates a {@code HysteresisLevels} object for ambient brightness.
      * @param brighteningThresholds an array of brightening hysteresis constraint constants.
      * @param darkeningThresholds an array of darkening hysteresis constraint constants.
      * @param thresholdLevels a monotonically increasing array of threshold levels.
@@ -59,6 +58,28 @@
     }
 
     /**
+     * Creates a {@code HysteresisLevels} object for screen brightness.
+     * @param brighteningThresholds an array of brightening hysteresis constraint constants.
+     * @param darkeningThresholds an array of darkening hysteresis constraint constants.
+     * @param thresholdLevels a monotonically increasing array of threshold levels.
+     * @param minBrighteningThreshold the minimum value for which the brightening value needs to
+     *                                return.
+     * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
+     */
+    HysteresisLevels(int[] brighteningThresholds, int[] darkeningThresholds,
+            float[] thresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold) {
+        if (brighteningThresholds.length != darkeningThresholds.length
+                || darkeningThresholds.length != thresholdLevels.length + 1) {
+            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+        }
+        mBrighteningThresholds = setArrayFormat(brighteningThresholds, 1000.0f);
+        mDarkeningThresholds = setArrayFormat(darkeningThresholds, 1000.0f);
+        mThresholdLevels = constraintInRangeIfNeeded(thresholdLevels);
+        mMinDarkening = minDarkeningThreshold;
+        mMinBrightening = minBrighteningThreshold;
+    }
+
+    /**
      * Return the brightening hysteresis threshold for the given value level.
      */
     public float getBrighteningThreshold(float value) {
@@ -104,11 +125,42 @@
     private float[] setArrayFormat(int[] configArray, float divideFactor) {
         float[] levelArray = new float[configArray.length];
         for (int index = 0; levelArray.length > index; ++index) {
-            levelArray[index] = (float)configArray[index] / divideFactor;
+            levelArray[index] = (float) configArray[index] / divideFactor;
         }
         return levelArray;
     }
 
+    /**
+     * This check is due to historical reasons, where screen thresholdLevels used to be
+     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+     * To accommodate both the possibilities, we first check if all the thresholdLevels are in [0,
+     * 1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+     */
+    private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+        if (isAllInRange(thresholdLevels, /* minValueInclusive = */ 0.0f, /* maxValueInclusive = */
+                1.0f)) {
+            return thresholdLevels;
+        }
+
+        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+        for (int index = 0; thresholdLevels.length > index; ++index) {
+            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+        }
+        return thresholdLevelsScaled;
+    }
+
+    private boolean isAllInRange(float[] configArray, float minValueInclusive,
+            float maxValueInclusive) {
+        int configArraySize = configArray.length;
+        for (int index = 0; configArraySize > index; ++index) {
+            if (configArray[index] < minValueInclusive || configArray[index] > maxValueInclusive) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     void dump(PrintWriter pw) {
         pw.println("HysteresisLevels");
         pw.println("  mBrighteningThresholds=" + Arrays.toString(mBrighteningThresholds));
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index b51f3f5..21a8518 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -50,6 +50,7 @@
 import android.hardware.display.ColorDisplayManager;
 import android.hardware.display.ColorDisplayManager.AutoMode;
 import android.hardware.display.ColorDisplayManager.ColorMode;
+import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.IColorDisplayManager;
 import android.hardware.display.Time;
 import android.net.Uri;
@@ -154,7 +155,8 @@
 
     @VisibleForTesting
     final DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController =
-            new DisplayWhiteBalanceTintController();
+            new DisplayWhiteBalanceTintController(
+                    LocalServices.getService(DisplayManagerInternal.class));
     private final NightDisplayTintController mNightDisplayTintController =
             new NightDisplayTintController();
     private final TintController mGlobalSaturationTintController =
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 93a78c1..c5dd6ac 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display.color;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
 
 import android.annotation.NonNull;
@@ -24,10 +26,9 @@
 import android.content.res.Resources;
 import android.graphics.ColorSpace;
 import android.hardware.display.ColorDisplayManager;
+import android.hardware.display.DisplayManagerInternal;
 import android.opengl.Matrix;
-import android.os.IBinder;
 import android.util.Slog;
-import android.view.SurfaceControl;
 import android.view.SurfaceControl.DisplayPrimaries;
 
 import com.android.internal.R;
@@ -64,6 +65,12 @@
     // This feature becomes disallowed if the device is in an unsupported strong/light state.
     private boolean mIsAllowed = true;
 
+    private final DisplayManagerInternal mDisplayManagerInternal;
+
+    DisplayWhiteBalanceTintController(DisplayManagerInternal dm) {
+        mDisplayManagerInternal = dm;
+    }
+
     @Override
     public void setUp(Context context, boolean needsLinear) {
         mSetUp = false;
@@ -287,12 +294,8 @@
     }
 
     private ColorSpace.Rgb getDisplayColorSpaceFromSurfaceControl() {
-        final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
-        if (displayToken == null) {
-            return null;
-        }
-
-        DisplayPrimaries primaries = SurfaceControl.getDisplayNativePrimaries(displayToken);
+        DisplayPrimaries primaries =
+                mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY);
         if (primaries == null || primaries.red == null || primaries.green == null
                 || primaries.blue == null || primaries.white == null) {
             return null;
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 1d1057f..9bd48f2 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -115,6 +115,7 @@
 import com.android.server.location.injector.LocationPermissionsHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper;
 import com.android.server.location.injector.SettingsHelper;
 import com.android.server.location.injector.SystemAlarmHelper;
@@ -125,6 +126,7 @@
 import com.android.server.location.injector.SystemEmergencyHelper;
 import com.android.server.location.injector.SystemLocationPermissionsHelper;
 import com.android.server.location.injector.SystemLocationPowerSaveModeHelper;
+import com.android.server.location.injector.SystemPackageResetHelper;
 import com.android.server.location.injector.SystemScreenInteractiveHelper;
 import com.android.server.location.injector.SystemSettingsHelper;
 import com.android.server.location.injector.SystemUserInfoHelper;
@@ -1696,11 +1698,13 @@
         private final SystemDeviceStationaryHelper mDeviceStationaryHelper;
         private final SystemDeviceIdleHelper mDeviceIdleHelper;
         private final LocationUsageLogger mLocationUsageLogger;
+        private final PackageResetHelper mPackageResetHelper;
 
         // lazily instantiated since they may not always be used
 
         @GuardedBy("this")
-        private @Nullable SystemEmergencyHelper mEmergencyCallHelper;
+        @Nullable
+        private SystemEmergencyHelper mEmergencyCallHelper;
 
         @GuardedBy("this")
         private boolean mSystemReady;
@@ -1721,6 +1725,7 @@
             mDeviceStationaryHelper = new SystemDeviceStationaryHelper();
             mDeviceIdleHelper = new SystemDeviceIdleHelper(context);
             mLocationUsageLogger = new LocationUsageLogger();
+            mPackageResetHelper = new SystemPackageResetHelper(context);
         }
 
         synchronized void onSystemReady() {
@@ -1811,5 +1816,10 @@
         public LocationUsageLogger getLocationUsageLogger() {
             return mLocationUsageLogger;
         }
+
+        @Override
+        public PackageResetHelper getPackageResetHelper() {
+            return mPackageResetHelper;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index 82bcca2..e7f6e67 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -39,6 +39,7 @@
 import com.android.server.location.injector.AppForegroundHelper;
 import com.android.server.location.injector.Injector;
 import com.android.server.location.injector.LocationPermissionsHelper;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.SettingsHelper;
 import com.android.server.location.injector.UserInfoHelper;
 import com.android.server.location.injector.UserInfoHelper.UserListener;
@@ -193,6 +194,7 @@
     protected final LocationPermissionsHelper mLocationPermissionsHelper;
     protected final AppForegroundHelper mAppForegroundHelper;
     protected final LocationManagerInternal mLocationManagerInternal;
+    private final PackageResetHelper mPackageResetHelper;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final ProviderEnabledListener mProviderEnabledChangedListener =
@@ -218,12 +220,25 @@
             };
     private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener =
             this::onAppForegroundChanged;
+    private final PackageResetHelper.Responder mPackageResetResponder =
+            new PackageResetHelper.Responder() {
+                @Override
+                public void onPackageReset(String packageName) {
+                    GnssListenerMultiplexer.this.onPackageReset(packageName);
+                }
+
+                @Override
+                public boolean isResetableForPackage(String packageName) {
+                    return GnssListenerMultiplexer.this.isResetableForPackage(packageName);
+                }
+            };
 
     protected GnssListenerMultiplexer(Injector injector) {
         mUserInfoHelper = injector.getUserInfoHelper();
         mSettingsHelper = injector.getSettingsHelper();
         mLocationPermissionsHelper = injector.getLocationPermissionsHelper();
         mAppForegroundHelper = injector.getAppForegroundHelper();
+        mPackageResetHelper = injector.getPackageResetHelper();
         mLocationManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(LocationManagerInternal.class));
     }
@@ -357,6 +372,7 @@
                 mLocationPackageBlacklistChangedListener);
         mLocationPermissionsHelper.addListener(mLocationPermissionsListener);
         mAppForegroundHelper.addListener(mAppForegroundChangedListener);
+        mPackageResetHelper.register(mPackageResetResponder);
     }
 
     @Override
@@ -374,6 +390,7 @@
                 mLocationPackageBlacklistChangedListener);
         mLocationPermissionsHelper.removeListener(mLocationPermissionsListener);
         mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
+        mPackageResetHelper.unregister(mPackageResetResponder);
     }
 
     private void onUserChanged(int userId, int change) {
@@ -407,6 +424,27 @@
         updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
     }
 
+    private void onPackageReset(String packageName) {
+        // invoked when a package is "force quit" - move off the main thread
+        FgThread.getExecutor().execute(
+                () ->
+                        updateRegistrations(
+                                registration -> {
+                                    if (registration.getIdentity().getPackageName().equals(
+                                            packageName)) {
+                                        registration.remove();
+                                    }
+
+                                    return false;
+                                }));
+    }
+
+    private boolean isResetableForPackage(String packageName) {
+        // invoked to find out if the given package has any state that can be "force quit"
+        return findRegistration(
+                registration -> registration.getIdentity().getPackageName().equals(packageName));
+    }
+
     @Override
     protected String getServiceState() {
         if (!isSupported()) {
diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java
index c0ce3a6..b2c8672 100644
--- a/services/core/java/com/android/server/location/injector/Injector.java
+++ b/services/core/java/com/android/server/location/injector/Injector.java
@@ -63,4 +63,7 @@
 
     /** Returns a LocationUsageLogger. */
     LocationUsageLogger getLocationUsageLogger();
+
+    /** Returns a PackageResetHelper. */
+    PackageResetHelper getPackageResetHelper();
 }
diff --git a/services/core/java/com/android/server/location/injector/PackageResetHelper.java b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
new file mode 100644
index 0000000..721c576
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/PackageResetHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.location.injector;
+
+import static com.android.server.location.LocationManagerService.D;
+import static com.android.server.location.LocationManagerService.TAG;
+
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/** Helpers for tracking queries and resets of package state. */
+public abstract class PackageResetHelper {
+
+    /** Interface for responding to reset events. */
+    public interface Responder {
+
+        /**
+         * Called when a package's runtime state is being reset for whatever reason and any
+         * components carrying runtime state on behalf of the package should clear that state.
+         *
+         * @param packageName The name of the package.
+         */
+        void onPackageReset(String packageName);
+
+        /**
+         * Called when the system queries whether this package has any active state for the given
+         * package. Should return true if the component has some runtime state that is resetable of
+         * behalf of the given package, and false otherwise.
+         *
+         * @param packageName The name of the package.
+         * @return True if this component has resetable state for the given package.
+         */
+        boolean isResetableForPackage(String packageName);
+    }
+
+    private final CopyOnWriteArrayList<Responder> mResponders;
+
+    public PackageResetHelper() {
+        mResponders = new CopyOnWriteArrayList<>();
+    }
+
+    /** Begin listening for package reset events. */
+    public synchronized void register(Responder responder) {
+        boolean empty = mResponders.isEmpty();
+        mResponders.add(responder);
+        if (empty) {
+            onRegister();
+        }
+    }
+
+    /** Stop listening for package reset events. */
+    public synchronized void unregister(Responder responder) {
+        mResponders.remove(responder);
+        if (mResponders.isEmpty()) {
+            onUnregister();
+        }
+    }
+
+    @GuardedBy("this")
+    protected abstract void onRegister();
+
+    @GuardedBy("this")
+    protected abstract void onUnregister();
+
+    protected final void notifyPackageReset(String packageName) {
+        if (D) {
+            Log.d(TAG, "package " + packageName + " reset");
+        }
+
+        for (Responder responder : mResponders) {
+            responder.onPackageReset(packageName);
+        }
+    }
+
+    protected final boolean queryResetableForPackage(String packageName) {
+        for (Responder responder : mResponders) {
+            if (responder.isResetableForPackage(packageName)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
new file mode 100644
index 0000000..91b0212
--- /dev/null
+++ b/services/core/java/com/android/server/location/injector/SystemPackageResetHelper.java
@@ -0,0 +1,140 @@
+/*
+ * 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.location.injector;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+
+import com.android.internal.util.Preconditions;
+
+/** Listens to appropriate broadcasts for queries and resets. */
+public class SystemPackageResetHelper extends PackageResetHelper {
+
+    private final Context mContext;
+
+    @Nullable
+    private BroadcastReceiver mReceiver;
+
+    public SystemPackageResetHelper(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    protected void onRegister() {
+        Preconditions.checkState(mReceiver == null);
+        mReceiver = new Receiver();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+        filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        filter.addDataScheme("package");
+
+        // We don't filter for Intent.ACTION_PACKAGE_DATA_CLEARED as 1) it refers to persistent
+        // data, and 2) it should always be preceded by Intent.ACTION_PACKAGE_RESTARTED, which
+        // refers to runtime data. in this way we also avoid redundant callbacks.
+
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    @Override
+    protected void onUnregister() {
+        Preconditions.checkState(mReceiver != null);
+        mContext.unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
+
+    private class Receiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) {
+                return;
+            }
+
+            Uri data = intent.getData();
+            if (data == null) {
+                return;
+            }
+
+            String packageName = data.getSchemeSpecificPart();
+            if (packageName == null) {
+                return;
+            }
+
+            switch (action) {
+                case Intent.ACTION_QUERY_PACKAGE_RESTART:
+                    String[] packages = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                    if (packages != null) {
+                        // it would be more efficient to pass through the whole array, but at the
+                        // moment the array is always size 1, and this makes for a nicer callback.
+                        for (String pkg : packages) {
+                            if (queryResetableForPackage(pkg)) {
+                                setResultCode(Activity.RESULT_OK);
+                                break;
+                            }
+                        }
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_CHANGED:
+                    // make sure this is an enabled/disabled change to the package as a whole, not
+                    // just some of its components. This avoids unnecessary work in the callback.
+                    boolean isPackageChange = false;
+                    String[] components = intent.getStringArrayExtra(
+                            Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                    if (components != null) {
+                        for (String component : components) {
+                            if (packageName.equals(component)) {
+                                isPackageChange = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (isPackageChange) {
+                        try {
+                            ApplicationInfo appInfo =
+                                    context.getPackageManager().getApplicationInfo(packageName,
+                                            PackageManager.ApplicationInfoFlags.of(0));
+                            if (!appInfo.enabled) {
+                                notifyPackageReset(packageName);
+                            }
+                        } catch (PackageManager.NameNotFoundException e) {
+                            return;
+                        }
+                    }
+                    break;
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    // fall through
+                case Intent.ACTION_PACKAGE_RESTARTED:
+                    notifyPackageReset(packageName);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index a69a079..bd75251 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -106,6 +106,7 @@
 import com.android.server.location.injector.LocationPowerSaveModeHelper;
 import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener;
 import com.android.server.location.injector.LocationUsageLogger;
+import com.android.server.location.injector.PackageResetHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper;
 import com.android.server.location.injector.ScreenInteractiveHelper.ScreenInteractiveChangedListener;
 import com.android.server.location.injector.SettingsHelper;
@@ -1373,6 +1374,7 @@
     protected final ScreenInteractiveHelper mScreenInteractiveHelper;
     protected final LocationUsageLogger mLocationUsageLogger;
     protected final LocationFudger mLocationFudger;
+    private final PackageResetHelper mPackageResetHelper;
 
     private final UserListener mUserChangedListener = this::onUserChanged;
     private final LocationSettings.LocationUserSettingsListener mLocationUserSettingsListener =
@@ -1407,6 +1409,18 @@
             this::onLocationPowerSaveModeChanged;
     private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
             this::onScreenInteractiveChanged;
+    private final PackageResetHelper.Responder mPackageResetResponder =
+            new PackageResetHelper.Responder() {
+                @Override
+                public void onPackageReset(String packageName) {
+                    LocationProviderManager.this.onPackageReset(packageName);
+                }
+
+                @Override
+                public boolean isResetableForPackage(String packageName) {
+                    return LocationProviderManager.this.isResetableForPackage(packageName);
+                }
+            };
 
     // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
     protected final MockableLocationProvider mProvider;
@@ -1442,6 +1456,7 @@
         mScreenInteractiveHelper = injector.getScreenInteractiveHelper();
         mLocationUsageLogger = injector.getLocationUsageLogger();
         mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
+        mPackageResetHelper = injector.getPackageResetHelper();
 
         mProvider = new MockableLocationProvider(mMultiplexerLock);
 
@@ -1970,6 +1985,7 @@
         mAppForegroundHelper.addListener(mAppForegroundChangedListener);
         mLocationPowerSaveModeHelper.addListener(mLocationPowerSaveModeChangedListener);
         mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
+        mPackageResetHelper.register(mPackageResetResponder);
     }
 
     @GuardedBy("mMultiplexerLock")
@@ -1988,6 +2004,7 @@
         mAppForegroundHelper.removeListener(mAppForegroundChangedListener);
         mLocationPowerSaveModeHelper.removeListener(mLocationPowerSaveModeChangedListener);
         mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
+        mPackageResetHelper.unregister(mPackageResetResponder);
     }
 
     @GuardedBy("mMultiplexerLock")
@@ -2391,6 +2408,27 @@
         updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
     }
 
+    private void onPackageReset(String packageName) {
+        // invoked when a package is "force quit" - move off the main thread
+        FgThread.getExecutor().execute(
+                () ->
+                        updateRegistrations(
+                                registration -> {
+                                    if (registration.getIdentity().getPackageName().equals(
+                                            packageName)) {
+                                        registration.remove();
+                                    }
+
+                                    return false;
+                                }));
+    }
+
+    private boolean isResetableForPackage(String packageName) {
+        // invoked to find out if the given package has any state that can be "force quit"
+        return findRegistration(
+                registration -> registration.getIdentity().getPackageName().equals(packageName));
+    }
+
     @GuardedBy("mMultiplexerLock")
     @Override
     public void onStateChanged(
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index bfa8af9..92a6329 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -601,6 +601,26 @@
         }
     }
 
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        pw.println(prefix + "MediaRouter2ServiceImpl");
+
+        String indent = prefix + "  ";
+
+        synchronized (mLock) {
+            pw.println(indent + "mNextRouterOrManagerId=" + mNextRouterOrManagerId.get());
+            pw.println(indent + "mCurrentUserId=" + mCurrentUserId);
+
+            pw.println(indent + "UserRecords:");
+            if (mUserRecords.size() > 0) {
+                for (int i = 0; i < mUserRecords.size(); i++) {
+                    mUserRecords.get(i).dump(pw, indent + "  ");
+                }
+            } else {
+                pw.println(indent + "<no user records>");
+            }
+        }
+    }
+
     //TODO(b/136703681): Review this is handling multi-user properly.
     void switchUser() {
         synchronized (mLock) {
@@ -1197,6 +1217,41 @@
             }
             return null;
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "UserRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mUserId=" + mUserId);
+
+            pw.println(indent + "Router Records:");
+            if (!mRouterRecords.isEmpty()) {
+                for (RouterRecord routerRecord : mRouterRecords) {
+                    routerRecord.dump(pw, indent + "  ");
+                }
+            } else {
+                pw.println(indent + "<no router records>");
+            }
+
+            pw.println(indent + "Manager Records:");
+            if (!mManagerRecords.isEmpty()) {
+                for (ManagerRecord managerRecord : mManagerRecords) {
+                    managerRecord.dump(pw, indent + "  ");
+                }
+            } else {
+                pw.println(indent + "<no manager records>");
+            }
+
+            if (!mHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    mHandler.dump(pw, indent);
+                }
+            }, 1000)) {
+                pw.println(indent + "<could not dump handler state>");
+            }
+        }
     }
 
     final class RouterRecord implements IBinder.DeathRecipient {
@@ -1236,6 +1291,22 @@
         public void binderDied() {
             routerDied(this);
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "RouterRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mPackageName=" + mPackageName);
+            pw.println(indent + "mSelectRouteSequenceNumbers=" + mSelectRouteSequenceNumbers);
+            pw.println(indent + "mUid=" + mUid);
+            pw.println(indent + "mPid=" + mPid);
+            pw.println(indent + "mHasConfigureWifiDisplayPermission="
+                    + mHasConfigureWifiDisplayPermission);
+            pw.println(indent + "mHasModifyAudioRoutingPermission="
+                    + mHasModifyAudioRoutingPermission);
+            pw.println(indent + "mRouterId=" + mRouterId);
+        }
     }
 
     final class ManagerRecord implements IBinder.DeathRecipient {
@@ -1267,8 +1338,20 @@
             managerDied(this);
         }
 
-        public void dump(PrintWriter pw, String prefix) {
-            pw.println(prefix + this);
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "ManagerRecord");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mPackageName=" + mPackageName);
+            pw.println(indent + "mManagerId=" + mManagerId);
+            pw.println(indent + "mUid=" + mUid);
+            pw.println(indent + "mPid=" + mPid);
+            pw.println(indent + "mIsScanning=" + mIsScanning);
+
+            if (mLastSessionCreationRequest != null) {
+                mLastSessionCreationRequest.dump(pw, indent);
+            }
         }
 
         public void startScan() {
@@ -1455,6 +1538,15 @@
             }
         }
 
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "UserHandler");
+
+            String indent = prefix + "  ";
+            pw.println(indent + "mRunning=" + mRunning);
+
+            mWatcher.dump(pw, prefix);
+        }
+
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
 
@@ -2340,5 +2432,14 @@
             mOldSession = oldSession;
             mRoute = route;
         }
+
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            pw.println(prefix + "SessionCreationRequest");
+
+            String indent = prefix + "  ";
+
+            pw.println(indent + "mUniqueRequestId=" + mUniqueRequestId);
+            pw.println(indent + "mManagerRequestId=" + mManagerRequestId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4806b52..4f0da795 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -387,6 +387,9 @@
                 userRecord.dump(pw, "");
             }
         }
+
+        pw.println();
+        mService2.dump(pw, "");
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 14140b5..5fa5de7 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,7 +28,6 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -199,6 +198,7 @@
     protected SnapshotCache<WatchedSparseBooleanMatrix> mShouldFilterCacheSnapshot;
 
     protected volatile boolean mCacheReady = false;
+    protected volatile boolean mCacheEnabled = true;
 
     protected static final boolean CACHE_VALID = true;
     protected static final boolean CACHE_INVALID = false;
@@ -342,8 +342,7 @@
                       && !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
             }
             // use cache
-            if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
-                    true)) {
+            if (mCacheReady && mCacheEnabled) {
                 if (!shouldFilterApplicationUsingCache(callingUid,
                         targetPkgSetting.getAppId(),
                         userId)) {
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 79d72a3..4c21195 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -36,6 +36,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -223,6 +224,12 @@
                 return new AppsFilterSnapshotImpl(AppsFilterImpl.this);
             }
         };
+        readCacheEnabledSysProp();
+        SystemProperties.addChangeCallback(this::readCacheEnabledSysProp);
+    }
+
+    private void readCacheEnabledSysProp() {
+        mCacheEnabled = SystemProperties.getBoolean("debug.pm.use_app_filter_cache", true);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
index 019c853..4e268a2 100644
--- a/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterSnapshotImpl.java
@@ -74,6 +74,7 @@
             // cache is not ready, use an empty cache for the snapshot
             mShouldFilterCache = new WatchedSparseBooleanMatrix();
         }
+        mCacheEnabled = orig.mCacheEnabled;
         mShouldFilterCacheSnapshot = new SnapshotCache.Sealed<>();
 
         mBackgroundHandler = null;
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 797d4c3..f6472a7 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -33,11 +33,9 @@
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
 import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_FRAMEWORK_RES_SPLITS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.parsing.ApkLiteParseUtils;
 import android.os.Environment;
 import android.os.SystemClock;
 import android.os.Trace;
@@ -121,29 +119,6 @@
         mExecutorService = ParallelPackageParser.makeExecutorService();
     }
 
-    private List<File> getFrameworkResApkSplitFiles() {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanFrameworkResApkSplits");
-        try {
-            final List<File> splits = new ArrayList<>();
-            final List<ApexManager.ActiveApexInfo> activeApexInfos =
-                    mPm.mApexManager.getActiveApexInfos();
-            for (int i = 0; i < activeApexInfos.size(); i++) {
-                ApexManager.ActiveApexInfo apexInfo = activeApexInfos.get(i);
-                File splitsFolder = new File(apexInfo.apexDirectory, "etc/splits");
-                if (splitsFolder.isDirectory()) {
-                    for (File file : splitsFolder.listFiles()) {
-                        if (ApkLiteParseUtils.isApkFile(file)) {
-                            splits.add(file);
-                        }
-                    }
-                }
-            }
-            return splits;
-        } finally {
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
-        }
-    }
-
     private List<ScanPartition> getSystemScanPartitions() {
         final List<ScanPartition> scanPartitions = new ArrayList<>();
         scanPartitions.addAll(mSystemPartitions);
@@ -270,7 +245,7 @@
             long startTime) {
         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                 SystemClock.uptimeMillis());
-        scanDirTracedLI(mPm.getAppInstallDir(), /* frameworkSplits= */ null, 0,
+        scanDirTracedLI(mPm.getAppInstallDir(), 0,
                 mScanFlags | SCAN_REQUIRE_KNOWN, packageParser, mExecutorService);
 
         List<Runnable> unfinishedTasks = mExecutorService.shutdownNow();
@@ -338,15 +313,13 @@
             if (partition.getOverlayFolder() == null) {
                 continue;
             }
-            scanDirTracedLI(partition.getOverlayFolder(), /* frameworkSplits= */ null,
+            scanDirTracedLI(partition.getOverlayFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
 
-        List<File> frameworkSplits = getFrameworkResApkSplitFiles();
-        scanDirTracedLI(frameworkDir, frameworkSplits,
-                mSystemParseFlags | PARSE_FRAMEWORK_RES_SPLITS,
-                mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
+        scanDirTracedLI(frameworkDir,
+                mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,
                 packageParser, executorService);
         if (!mPm.mPackages.containsKey("android")) {
             throw new IllegalStateException(
@@ -356,12 +329,12 @@
         for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
             final ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.getPrivAppFolder() != null) {
-                scanDirTracedLI(partition.getPrivAppFolder(), /* frameworkSplits= */ null,
+                scanDirTracedLI(partition.getPrivAppFolder(),
                         mSystemParseFlags,
                         mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,
                         packageParser, executorService);
             }
-            scanDirTracedLI(partition.getAppFolder(), /* frameworkSplits= */ null,
+            scanDirTracedLI(partition.getAppFolder(),
                     mSystemParseFlags, mSystemScanFlags | partition.scanFlag,
                     packageParser, executorService);
         }
@@ -379,7 +352,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    private void scanDirTracedLI(File scanDir, List<File> frameworkSplits,
+    private void scanDirTracedLI(File scanDir,
             int parseFlags, int scanFlags,
             PackageParser2 packageParser, ExecutorService executorService) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
@@ -388,7 +361,7 @@
                 // when scanning apk in apexes, we want to check the maxSdkVersion
                 parseFlags |= PARSE_APK_IN_APEX;
             }
-            mInstallPackageHelper.installPackagesFromDir(scanDir, frameworkSplits, parseFlags,
+            mInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,
                     scanFlags, packageParser, executorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f6e5b69..1746d93 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3472,7 +3472,7 @@
     }
 
     @GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
-    public void installPackagesFromDir(File scanDir, List<File> frameworkSplits, int parseFlags,
+    public void installPackagesFromDir(File scanDir, int parseFlags,
             int scanFlags, PackageParser2 packageParser,
             ExecutorService executorService) {
         final File[] files = scanDir.listFiles();
@@ -3486,7 +3486,7 @@
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
         ParallelPackageParser parallelPackageParser =
-                new ParallelPackageParser(packageParser, executorService, frameworkSplits);
+                new ParallelPackageParser(packageParser, executorService);
 
         // Submit files for parsing in parallel
         int fileCount = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 09bc25b..9481f8a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6541,7 +6541,7 @@
                             if (dependentState == null) {
                                 continue;
                             }
-                            if (!Objects.equals(dependentState.getUserStateOrDefault(userId)
+                            if (canSetOverlayPaths(dependentState.getUserStateOrDefault(userId)
                                     .getSharedLibraryOverlayPaths()
                                     .get(libName), newOverlayPaths)) {
                                 String dependentPackageName = dependent.getPackageName();
@@ -6565,7 +6565,10 @@
                     }
                 }
 
-                outUpdatedPackageNames.add(targetPackageName);
+                if (canSetOverlayPaths(packageState.getUserStateOrDefault(userId).getOverlayPaths(),
+                        newOverlayPaths)) {
+                    outUpdatedPackageNames.add(targetPackageName);
+                }
             }
 
             commitPackageStateMutation(null, mutator -> {
@@ -6616,6 +6619,17 @@
         invalidatePackageInfoCache();
     }
 
+    private boolean canSetOverlayPaths(OverlayPaths origPaths, OverlayPaths newPaths) {
+        if (Objects.equals(origPaths, newPaths)) {
+            return false;
+        }
+        if ((origPaths == null && newPaths.isEmpty())
+                || (newPaths == null && origPaths.isEmpty())) {
+            return false;
+        }
+        return true;
+    }
+
     private void maybeUpdateSystemOverlays(String targetPackageName, OverlayPaths newOverlayPaths) {
         if (!mResolverReplaced) {
             if (targetPackageName.equals("android")) {
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 45030bf..5625884 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -27,7 +27,6 @@
 import com.android.server.pm.parsing.pkg.ParsedPackage;
 
 import java.io.File;
-import java.util.List;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
@@ -55,17 +54,9 @@
 
     private final ExecutorService mExecutorService;
 
-    private final List<File> mFrameworkSplits;
-
     ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService) {
-        this(packageParser, executorService, /* frameworkSplits= */ null);
-    }
-
-    ParallelPackageParser(PackageParser2 packageParser, ExecutorService executorService,
-            List<File> frameworkSplits) {
         mPackageParser = packageParser;
         mExecutorService = executorService;
-        mFrameworkSplits = frameworkSplits;
     }
 
     static class ParseResult {
@@ -134,6 +125,6 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true, mFrameworkSplits);
+        return mPackageParser.parsePackage(scanFile, parseFlags, true);
     }
 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3b3e1db..c77459d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2048,8 +2048,8 @@
         }
     }
 
-    @Override
-    public boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
+    @VisibleForTesting
+    boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
         boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.USER_SWITCHER_ENABLED,
                 Resources.getSystem().getBoolean(com.android.internal
@@ -2062,6 +2062,33 @@
     }
 
     @Override
+    public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable,
+            @UserIdInt int mUserId) {
+        if (!isUserSwitcherEnabled(mUserId)) {
+            return false;
+        }
+        // The feature is enabled. But is it worth showing?
+        return showEvenIfNotActionable
+                || !hasUserRestriction(UserManager.DISALLOW_ADD_USER, mUserId) // Can add new user
+                || areThereMultipleSwitchableUsers(); // There are switchable users
+    }
+
+    /** Returns true if there is more than one user that can be switched to. */
+    private boolean areThereMultipleSwitchableUsers() {
+        List<UserInfo> aliveUsers = getUsers(true, true, true);
+        boolean isAnyAliveUser = false;
+        for (UserInfo userInfo : aliveUsers) {
+            if (userInfo.supportsSwitchToByUser()) {
+                if (isAnyAliveUser) {
+                    return true;
+                }
+                isAnyAliveUser = true;
+            }
+        }
+        return false;
+    }
+
+    @Override
     public boolean isRestricted(@UserIdInt int userId) {
         if (userId != UserHandle.getCallingUserId()) {
             checkCreateUsersPermission("query isRestricted for user " + userId);
@@ -7072,4 +7099,5 @@
         }
         return mAmInternal;
     }
+
 }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 6caddaf..f5ba3f6 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -143,15 +143,6 @@
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
             throws PackageManagerException {
-        return parsePackage(packageFile, flags, useCaches, /* frameworkSplits= */ null);
-    }
-
-    /**
-     * TODO(b/135203078): Document new package parsing
-     */
-    @AnyThread
-    public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches,
-            List<File> frameworkSplits) throws PackageManagerException {
         var files = packageFile.listFiles();
         // Apk directory is directly nested under the current directory
         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -167,8 +158,7 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags,
-                frameworkSplits);
+        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
         if (result.isError()) {
             throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 14f787e..a8d48ae 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -238,7 +238,6 @@
      * of required system property within the overlay tag.
      */
     public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7;
-    public static final int PARSE_FRAMEWORK_RES_SPLITS = 1 << 8;
     public static final int PARSE_APK_IN_APEX = 1 << 9;
 
     public static final int PARSE_CHATTY = 1 << 31;
@@ -257,7 +256,6 @@
             PARSE_IS_SYSTEM_DIR,
             PARSE_MUST_BE_APK,
             PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY,
-            PARSE_FRAMEWORK_RES_SPLITS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ParseFlags {}
@@ -307,7 +305,7 @@
                         isCoreApp);
             }
         });
-        var parseResult = parser.parsePackage(input, file, parseFlags, /* frameworkSplits= */ null);
+        var parseResult = parser.parsePackage(input, file, parseFlags);
         if (parseResult.isError()) {
             return input.error(parseResult);
         }
@@ -356,14 +354,9 @@
      * not check whether {@code packageFile} has changed since the last parse, it's up to callers to
      * do so.
      */
-    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags,
-            List<File> frameworkSplits) {
-        if (((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0)
-                && frameworkSplits.size() > 0
-                && packageFile.getAbsolutePath().endsWith("/framework-res.apk")) {
-            return parseClusterPackage(input, packageFile, frameworkSplits, flags);
-        } else if (packageFile.isDirectory()) {
-            return parseClusterPackage(input, packageFile, /* frameworkSplits= */null, flags);
+    public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {
+        if (packageFile.isDirectory()) {
+            return parseClusterPackage(input, packageFile,  flags);
         } else {
             return parseMonolithicPackage(input, packageFile, flags);
         }
@@ -375,28 +368,17 @@
      * identical package name and version codes, a single base APK, and unique
      * split names.
      * <p>
-     * Can also be passed the framework-res.apk file and a list of split apks coming from apexes
-     * (via {@code frameworkSplits}) in which case they will be parsed similar to cluster packages
-     * even if they are in different folders. Note that this code path may have other behaviour
-     * differences.
-     * <p>
      * Note that this <em>does not</em> perform signature verification; that must be done separately
      * in {@link #getSigningDetails(ParseInput, ParsedPackage, boolean)}.
      */
     private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,
-            List<File> frameworkSplits, int flags) {
-        // parseClusterPackageLite should receive no flags (0) for regular splits but we want to
-        // pass the flags for framework splits
+            int flags) {
         int liteParseFlags = 0;
-        if ((flags & PARSE_FRAMEWORK_RES_SPLITS) != 0) {
-            liteParseFlags = flags;
-        }
         if ((flags & PARSE_APK_IN_APEX) != 0) {
             liteParseFlags |= PARSE_APK_IN_APEX;
         }
         final ParseResult<PackageLite> liteResult =
-                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, frameworkSplits,
-                        liteParseFlags);
+                ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);
         if (liteResult.isError()) {
             return input.error(liteResult);
         }
@@ -647,7 +629,7 @@
         final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
         try {
             final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,
-                    "coreApp",false);
+                    "coreApp", false);
             final ParsingPackage pkg = mCallback.startParsingPackage(
                     pkgName, apkPath, codePath, manifestArray, isCoreApp);
             final ParseResult<ParsingPackage> result =
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerService.java b/services/core/java/com/android/server/resources/ResourcesManagerService.java
index cc27546..eec3a02 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerService.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerService.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.IResourcesManager;
+import android.content.res.ResourceTimer;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -55,7 +56,7 @@
 
     @Override
     public void onStart() {
-        // Intentionally left empty.
+        ResourceTimer.start();
     }
 
     private final IBinder mService = new IResourcesManager.Stub() {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e21feae..886e8e6 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -93,6 +93,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 
@@ -254,6 +255,7 @@
             return;
         }
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            checkNewAgents();
             mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
             mReceiver.register(mContext);
             mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
@@ -262,7 +264,7 @@
             refreshAgentList(UserHandle.USER_ALL);
             refreshDeviceLockedForUser(UserHandle.USER_ALL);
         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
-            maybeEnableFactoryTrustAgents(mLockPatternUtils, UserHandle.USER_SYSTEM);
+            maybeEnableFactoryTrustAgents(UserHandle.USER_SYSTEM);
         }
     }
 
@@ -685,7 +687,7 @@
      */
     public void lockUser(int userId) {
         mLockPatternUtils.requireStrongAuth(
-                StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, userId);
+                StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, userId);
         try {
             WindowManagerGlobal.getWindowManagerService().lockNow(null);
         } catch (RemoteException e) {
@@ -1083,7 +1085,7 @@
         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
     }
 
-    private void maybeEnableFactoryTrustAgents(LockPatternUtils utils, int userId) {
+    private void maybeEnableFactoryTrustAgents(int userId) {
         if (0 != Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.TRUST_AGENTS_INITIALIZED, 0, userId)) {
             return;
@@ -1100,8 +1102,7 @@
         } else { // A default agent is not set; perform regular trust agent discovery
             for (ResolveInfo resolveInfo : resolveInfos) {
                 ComponentName componentName = getComponentName(resolveInfo);
-                int applicationInfoFlags = resolveInfo.serviceInfo.applicationInfo.flags;
-                if ((applicationInfoFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                if (!isSystemTrustAgent(resolveInfo)) {
                     Log.i(TAG, "Leaving agent " + componentName + " disabled because package "
                             + "is not a system package.");
                     continue;
@@ -1110,13 +1111,88 @@
             }
         }
 
-        List<ComponentName> previouslyEnabledAgents = utils.getEnabledTrustAgents(userId);
-        discoveredAgents.addAll(previouslyEnabledAgents);
-        utils.setEnabledTrustAgents(discoveredAgents, userId);
+        enableNewAgents(discoveredAgents, userId);
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, userId);
     }
 
+    private void checkNewAgents() {
+        for (UserInfo userInfo : mUserManager.getAliveUsers()) {
+            checkNewAgentsForUser(userInfo.id);
+        }
+    }
+
+    /**
+     * Checks for any new trust agents that become available after the first boot, add them to the
+     * list of known agents, and enable them if they should be enabled by default.
+     */
+    private void checkNewAgentsForUser(int userId) {
+        // When KNOWN_TRUST_AGENTS_INITIALIZED is not set, only update the known agent list but do
+        // not enable any agent.
+        // These agents will be enabled by #maybeEnableFactoryTrustAgents if this is the first time
+        // that this device boots and TRUST_AGENTS_INITIALIZED is not already set.
+        // Otherwise, these agents may have been manually disabled by the user, and we should not
+        // re-enable them.
+        if (0 == Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 0, userId)) {
+            initializeKnownAgents(userId);
+            return;
+        }
+
+        List<ComponentName> knownAgents = mLockPatternUtils.getKnownTrustAgents(userId);
+        List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(),
+                userId);
+        ArraySet<ComponentName> newAgents = new ArraySet<>(agentInfoList.size());
+        ArraySet<ComponentName> newSystemAgents = new ArraySet<>(agentInfoList.size());
+
+        for (ResolveInfo agentInfo : agentInfoList) {
+            ComponentName agentComponentName = getComponentName(agentInfo);
+            if (knownAgents.contains(agentComponentName)) {
+                continue;
+            }
+            newAgents.add(agentComponentName);
+            if (isSystemTrustAgent(agentInfo)) {
+                newSystemAgents.add(agentComponentName);
+            }
+        }
+
+        if (newAgents.isEmpty()) {
+            return;
+        }
+
+        ArraySet<ComponentName> updatedKnowAgents = new ArraySet<>(knownAgents);
+        updatedKnowAgents.addAll(newAgents);
+        mLockPatternUtils.setKnownTrustAgents(updatedKnowAgents, userId);
+
+        // Do not auto enable new trust agents when the default agent is set
+        boolean hasDefaultAgent = getDefaultFactoryTrustAgent(mContext) != null;
+        if (!hasDefaultAgent) {
+            enableNewAgents(newSystemAgents, userId);
+        }
+    }
+
+    private void enableNewAgents(Collection<ComponentName> agents, int userId) {
+        if (agents.isEmpty()) {
+            return;
+        }
+
+        ArraySet<ComponentName> agentsToEnable = new ArraySet<>(agents);
+        agentsToEnable.addAll(mLockPatternUtils.getEnabledTrustAgents(userId));
+        mLockPatternUtils.setEnabledTrustAgents(agentsToEnable, userId);
+    }
+
+    private void initializeKnownAgents(int userId) {
+        List<ResolveInfo> agentInfoList = resolveAllowedTrustAgents(mContext.getPackageManager(),
+                userId);
+        ArraySet<ComponentName> agentComponentNames = new ArraySet<>(agentInfoList.size());
+        for (ResolveInfo agentInfo : agentInfoList) {
+            agentComponentNames.add(getComponentName(agentInfo));
+        }
+        mLockPatternUtils.setKnownTrustAgents(agentComponentNames, userId);
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, userId);
+    }
+
     /**
      * Returns the {@link ComponentName} for the default trust agent, or {@code null} if there
      * is no trust agent set.
@@ -1152,6 +1228,10 @@
         return allowedAgents;
     }
 
+    private static boolean isSystemTrustAgent(ResolveInfo agentInfo) {
+        return (agentInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
     // Agent dispatch and aggregation
 
     private boolean aggregateIsTrusted(int userId) {
@@ -1825,7 +1905,13 @@
         }
 
         @Override
+        public void onPackageAdded(String packageName, int uid) {
+            checkNewAgentsForUser(UserHandle.getUserId(uid));
+        }
+
+        @Override
         public boolean onPackageChanged(String packageName, int uid, String[] components) {
+            checkNewAgentsForUser(UserHandle.getUserId(uid));
             // We're interested in all changes, even if just some components get enabled / disabled.
             return true;
         }
@@ -1860,7 +1946,7 @@
                     action)) {
                 int userId = getUserId(intent);
                 if (userId > 0) {
-                    maybeEnableFactoryTrustAgents(mLockPatternUtils, userId);
+                    maybeEnableFactoryTrustAgents(userId);
                 }
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 int userId = getUserId(intent);
@@ -1997,7 +2083,7 @@
             if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
                 if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
                 mLockPatternUtils.requireStrongAuth(
-                        mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST, mUserId);
+                        mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
             }
             maybeLockScreen(mUserId);
         }
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7067ae4..08d2e69 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -237,9 +237,21 @@
             if (mAssociatedTransitionInfo == null) {
                 launchResult = ":failed";
             } else {
-                launchResult = (abort ? ":canceled:" : mAssociatedTransitionInfo.mProcessSwitch
-                        ? ":completed:" : ":completed-same-process:")
-                        + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
+                final String status;
+                if (abort) {
+                    status = ":canceled:";
+                } else if (!mAssociatedTransitionInfo.mProcessSwitch) {
+                    status = ":completed-same-process:";
+                } else {
+                    if (endInfo.mTransitionType == TYPE_TRANSITION_HOT_LAUNCH) {
+                        status = ":completed-hot:";
+                    } else if (endInfo.mTransitionType == TYPE_TRANSITION_WARM_LAUNCH) {
+                        status = ":completed-warm:";
+                    } else {
+                        status = ":completed-cold:";
+                    }
+                }
+                launchResult = status + mAssociatedTransitionInfo.mLastLaunchedActivity.packageName;
             }
             // Put a supplement trace as the description of the async trace with the same id.
             Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, mTraceName + launchResult);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 98d3adc..d8d75ed 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1586,11 +1586,19 @@
 
         if (oldParent != null) {
             oldParent.cleanUpActivityReferences(this);
+            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+            // client side if needed.
+            oldParent.onActivityVisibleRequestedChanged();
         }
 
-        if (newParent != null && isState(RESUMED)) {
-            newParent.setResumedActivity(this, "onParentChanged");
-            mImeInsetsFrozenUntilStartInput = false;
+        if (newParent != null) {
+            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
+            // client side if needed.
+            newParent.onActivityVisibleRequestedChanged();
+            if (isState(RESUMED)) {
+                newParent.setResumedActivity(this, "onParentChanged");
+                mImeInsetsFrozenUntilStartInput = false;
+            }
         }
 
         if (rootTask != null && rootTask.topRunningActivity() == this) {
@@ -5094,6 +5102,10 @@
             return;
         }
         mVisibleRequested = visible;
+        final TaskFragment taskFragment = getTaskFragment();
+        if (taskFragment != null) {
+            taskFragment.onActivityVisibleRequestedChanged();
+        }
         setInsetsFrozen(!visible);
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
@@ -9550,7 +9562,7 @@
 
     @Override
     boolean showToCurrentUser() {
-        return mShowForAllUsers || mWmService.isCurrentProfile(mUserId);
+        return mShowForAllUsers || mWmService.isUserVisible(mUserId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bc1c6b2..a0a4f76 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -892,9 +892,10 @@
 
         final int userId = aInfo != null && aInfo.applicationInfo != null
                 ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+        final int launchMode = aInfo != null ? aInfo.launchMode : 0;
         if (err == ActivityManager.START_SUCCESS) {
             Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
-                    + "} from uid " + callingUid);
+                    + "} with " + launchModeToString(launchMode) + " from uid " + callingUid);
         }
 
         ActivityRecord sourceRecord = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 28cd001..5bddae6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1183,10 +1183,14 @@
         }
 
         if (!displayContent.isPrivate()) {
-            // Anyone can launch on a public display.
-            ProtoLog.d(WM_DEBUG_TASKS, "Launch on display check: allow launch on public "
-                    + "display");
-            return true;
+            // Checks if the caller can be shown in the given public display.
+            int userId = UserHandle.getUserId(callingUid);
+            int displayId = display.getDisplayId();
+            boolean allowed = mWindowManager.mUmInternal.isUserVisible(userId, displayId);
+            ProtoLog.d(WM_DEBUG_TASKS,
+                    "Launch on display check: %s launch for userId=%d on displayId=%d",
+                    (allowed ? "allow" : "disallow"), userId, displayId);
+            return allowed;
         }
 
         // Check if the caller is the owner of the display.
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index dcc16eb..d54f77a 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -19,17 +19,16 @@
 
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
-import static com.android.server.wm.WindowContainerProto.IDENTIFIER;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateProto.IDENTIFIER;
 
 import android.annotation.Nullable;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.proto.ProtoOutputStream;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 837045c..829f01e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -186,6 +186,7 @@
 import android.window.ITaskOrganizer;
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.StartingWindowInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
 
@@ -2679,6 +2680,7 @@
         if (isRootTask()) {
             updateSurfaceBounds();
         }
+        sendTaskFragmentParentInfoChangedIfNeeded();
     }
 
     boolean isResizeable() {
@@ -2914,7 +2916,7 @@
     @Override
     boolean showToCurrentUser() {
         return mForceShowForAllUsers || showForAllUsers()
-                || mWmService.isCurrentProfile(getTopMostTask().mUserId);
+                || mWmService.isUserVisible(getTopMostTask().mUserId);
     }
 
     void setForceShowForAllUsers(boolean forceShowForAllUsers) {
@@ -3513,6 +3515,33 @@
         return info;
     }
 
+    /**
+     * Returns the {@link TaskFragmentParentInfo} which will send to the client
+     * {@link android.window.TaskFragmentOrganizer}
+     */
+    TaskFragmentParentInfo getTaskFragmentParentInfo() {
+        return new TaskFragmentParentInfo(getConfiguration(), getDisplayId(), isVisibleRequested());
+    }
+
+    @Override
+    void onActivityVisibleRequestedChanged() {
+        if (mVisibleRequested != isVisibleRequested()) {
+            sendTaskFragmentParentInfoChangedIfNeeded();
+        }
+    }
+
+    void sendTaskFragmentParentInfoChangedIfNeeded() {
+        if (!isLeafTask()) {
+            // Only send parent info changed event for leaf task.
+            return;
+        }
+        final TaskFragment childOrganizedTf =
+                getTaskFragment(TaskFragment::isOrganizedTaskFragment);
+        if (childOrganizedTf != null) {
+            childOrganizedTf.sendTaskFragmentParentInfoChanged();
+        }
+    }
+
     boolean isTaskId(int taskId) {
         return mTaskId == taskId;
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index de4c84c..2cfc563 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -298,6 +298,9 @@
 
     final Point mLastSurfaceSize = new Point();
 
+    /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */
+    boolean mVisibleRequested;
+
     private final Rect mTmpBounds = new Rect();
     private final Rect mTmpFullBounds = new Rect();
     /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
@@ -2382,6 +2385,14 @@
         }
     }
 
+    void sendTaskFragmentParentInfoChanged() {
+        final Task parentTask = getParent().asTask();
+        if (mTaskFragmentOrganizer != null && parentTask != null) {
+            mTaskFragmentOrganizerController
+                    .onTaskFragmentParentInfoChanged(mTaskFragmentOrganizer, parentTask);
+        }
+    }
+
     private void sendTaskFragmentAppeared() {
         if (mTaskFragmentOrganizer != null) {
             mTaskFragmentOrganizerController.onTaskFragmentAppeared(mTaskFragmentOrganizer, this);
@@ -2417,7 +2428,7 @@
                 mRemoteToken.toWindowContainerToken(),
                 getConfiguration(),
                 getNonFinishingActivityCount(),
-                isVisible(),
+                isVisibleRequested(),
                 childActivities,
                 positionInParent,
                 mClearedTaskForReuse,
@@ -2669,6 +2680,18 @@
         return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
     }
 
+    void onActivityVisibleRequestedChanged() {
+        final boolean isVisibleRequested = isVisibleRequested();
+        if (mVisibleRequested == isVisibleRequested) {
+            return;
+        }
+        mVisibleRequested = isVisibleRequested;
+        final TaskFragment parentTf = getParent().asTaskFragment();
+        if (parentTf != null) {
+            parentTf.onActivityVisibleRequestedChanged();
+        }
+    }
+
     String toFullString() {
         final StringBuilder sb = new StringBuilder(128);
         sb.append(this);
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 8c037a7..2d5c989 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -49,6 +49,7 @@
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
 
@@ -118,10 +119,10 @@
         private final Map<TaskFragment, Integer> mTaskFragmentTaskIds = new WeakHashMap<>();
 
         /**
-         * Map from {@link Task#mTaskId} to the last Task {@link Configuration} sent to the
+         * Map from {@link Task#mTaskId} to the last {@link TaskFragmentParentInfo} sent to the
          * organizer.
          */
-        private final SparseArray<Configuration> mLastSentTaskFragmentParentConfigs =
+        private final SparseArray<TaskFragmentParentInfo> mLastSentTaskFragmentParentInfos =
                 new SparseArray<>();
 
         /**
@@ -225,7 +226,7 @@
                 taskId = mTaskFragmentTaskIds.remove(tf);
                 if (!mTaskFragmentTaskIds.containsValue(taskId)) {
                     // No more TaskFragment in the Task.
-                    mLastSentTaskFragmentParentConfigs.remove(taskId);
+                    mLastSentTaskFragmentParentInfos.remove(taskId);
                 }
             } else {
                 // This can happen if the appeared wasn't sent before remove.
@@ -260,25 +261,27 @@
         }
 
         @Nullable
-        TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(
-                @NonNull Task task) {
+        TaskFragmentTransaction.Change prepareTaskFragmentParentInfoChanged(@NonNull Task task) {
             final int taskId = task.mTaskId;
             // Check if the parent info is different from the last reported parent info.
-            final Configuration taskConfig = task.getConfiguration();
-            final Configuration lastParentConfig = mLastSentTaskFragmentParentConfigs.get(taskId);
-            if (configurationsAreEqualForOrganizer(taskConfig, lastParentConfig)
-                    && taskConfig.windowConfiguration.getWindowingMode()
-                    == lastParentConfig.windowConfiguration.getWindowingMode()) {
+            final TaskFragmentParentInfo parentInfo = task.getTaskFragmentParentInfo();
+            final TaskFragmentParentInfo lastParentInfo = mLastSentTaskFragmentParentInfos
+                    .get(taskId);
+            final Configuration lastParentConfig = lastParentInfo != null
+                    ? lastParentInfo.getConfiguration() : null;
+            if (parentInfo.equalsForTaskFragmentOrganizer(lastParentInfo)
+                    && configurationsAreEqualForOrganizer(parentInfo.getConfiguration(),
+                            lastParentConfig)) {
                 return null;
             }
 
             ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
                     "TaskFragment parent info changed name=%s parentTaskId=%d",
                     task.getName(), taskId);
-            mLastSentTaskFragmentParentConfigs.put(taskId, new Configuration(taskConfig));
+            mLastSentTaskFragmentParentInfos.put(taskId, new TaskFragmentParentInfo(parentInfo));
             return new TaskFragmentTransaction.Change(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED)
                     .setTaskId(taskId)
-                    .setTaskConfiguration(taskConfig);
+                    .setTaskFragmentParentInfo(parentInfo);
         }
 
         @NonNull
@@ -646,6 +649,34 @@
                 .build());
     }
 
+    void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer,
+            @NonNull Task task) {
+        validateAndGetState(organizer);
+        final PendingTaskFragmentEvent pendingEvent = getLastPendingParentInfoChangedEvent(
+                organizer, task);
+        if (pendingEvent == null) {
+            addPendingEvent(new PendingTaskFragmentEvent.Builder(
+                    PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED, organizer)
+                    .setTask(task)
+                    .build());
+        }
+    }
+
+    @Nullable
+    private PendingTaskFragmentEvent getLastPendingParentInfoChangedEvent(
+            @NonNull ITaskFragmentOrganizer organizer, @NonNull Task task) {
+        final List<PendingTaskFragmentEvent> events = mPendingTaskFragmentEvents
+                .get(organizer.asBinder());
+        for (int i = events.size() - 1; i >= 0; i--) {
+            final PendingTaskFragmentEvent event = events.get(i);
+            if (task == event.mTask
+                    && event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
+                return event;
+            }
+        }
+        return null;
+    }
+
     private void addPendingEvent(@NonNull PendingTaskFragmentEvent event) {
         mPendingTaskFragmentEvents.get(event.mTaskFragmentOrg.asBinder()).add(event);
     }
@@ -848,7 +879,9 @@
     }
 
     private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
-        if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) {
+        if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR
+                // Always send parent info changed to update task visibility
+                || event.mEventType == PendingTaskFragmentEvent.EVENT_PARENT_INFO_CHANGED) {
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 723aa19..93d9b0e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -42,6 +42,7 @@
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -1860,14 +1861,12 @@
                     // Whether this is in a Task with embedded activity.
                     flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
                 }
-                final Rect taskBounds = parentTask.getBounds();
-                final Rect startBounds = mAbsoluteBounds;
-                final Rect endBounds = wc.getBounds();
-                if (taskBounds.width() == startBounds.width()
-                        && taskBounds.height() == startBounds.height()
-                        && taskBounds.width() == endBounds.width()
-                        && taskBounds.height() == endBounds.height()) {
-                    // Whether the container fills the Task bounds before and after the transition.
+                if (parentTask.forAllActivities(ActivityRecord::hasStartingWindow)) {
+                    // The starting window should cover all windows inside the leaf Task.
+                    flags |= FLAG_IS_BEHIND_STARTING_WINDOW;
+                }
+                if (isWindowFillingTask(wc, parentTask)) {
+                    // Whether the container fills its parent Task bounds.
                     flags |= FLAG_FILLS_TASK;
                 }
             }
@@ -1889,6 +1888,22 @@
             }
             return flags;
         }
+
+        /** Whether the container fills its parent Task bounds before and after the transition. */
+        private boolean isWindowFillingTask(@NonNull WindowContainer wc, @NonNull Task parentTask) {
+            final Rect taskBounds = parentTask.getBounds();
+            final int taskWidth = taskBounds.width();
+            final int taskHeight = taskBounds.height();
+            final Rect startBounds = mAbsoluteBounds;
+            final Rect endBounds = wc.getBounds();
+            // Treat it as filling the task if it is not visible.
+            final boolean isInvisibleOrFillingTaskBeforeTransition = !mVisible
+                    || (taskWidth == startBounds.width() && taskHeight == startBounds.height());
+            final boolean isInVisibleOrFillingTaskAfterTransition = !wc.isVisibleRequested()
+                    || (taskWidth == endBounds.width() && taskHeight == endBounds.height());
+            return isInvisibleOrFillingTaskBeforeTransition
+                    && isInVisibleOrFillingTaskAfterTransition;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 8cc0d5d..69d86b6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2195,6 +2195,17 @@
                 callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound);
     }
 
+    @Nullable
+    TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            final TaskFragment tf = mChildren.get(i).getTaskFragment(callback);
+            if (tf != null) {
+                return tf;
+            }
+        }
+        return null;
+    }
+
     WindowState getWindow(Predicate<WindowState> callback) {
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final WindowState w = mChildren.get(i).getWindow(callback);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 49e7a0c..43ef1d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -123,7 +123,10 @@
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
@@ -155,6 +158,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
@@ -315,6 +319,7 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
@@ -504,15 +509,9 @@
     };
 
     /**
-     * Current user when multi-user is enabled. Don't show windows of
-     * non-current user. Also see mCurrentProfileIds.
+     * Current user when multi-user is enabled. Don't show windows of non-current user.
      */
-    int mCurrentUserId;
-    /**
-     * Users that are profiles of the current user. These are also allowed to show windows
-     * on the current user.
-     */
-    int[] mCurrentProfileIds = new int[] {};
+    @UserIdInt int mCurrentUserId;
 
     final Context mContext;
 
@@ -534,6 +533,7 @@
 
     final IActivityManager mActivityManager;
     final ActivityManagerInternal mAmInternal;
+    final UserManagerInternal mUmInternal;
 
     final AppOpsManager mAppOps;
     final PackageManagerInternal mPmInternal;
@@ -1263,6 +1263,7 @@
 
         mActivityManager = ActivityManager.getService();
         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mUmInternal = LocalServices.getService(UserManagerInternal.class);
         mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
         AppOpsManager.OnOpChangedInternalListener opListener =
                 new AppOpsManager.OnOpChangedInternalListener() {
@@ -2604,10 +2605,22 @@
         if (win.isWinVisibleLw() && win.mDisplayContent.okToAnimate()) {
             String reason = null;
             if (winAnimator.applyAnimationLocked(transit, false)) {
+                // This is a WMCore-driven window animation.
                 reason = "applyAnimation";
                 focusMayChange = true;
                 win.mAnimatingExit = true;
-            } else if (win.isExitAnimationRunningSelfOrParent()) {
+            } else if (
+                    // This is already animating via a WMCore-driven window animation
+                    win.isSelfAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION)
+                    // Or already animating as part of a legacy app-transition
+                    || win.isAnimating(PARENTS | TRANSITION,
+                            ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
+                    // Or already animating as part of a shell-transition.
+                    || (win.inTransition()
+                            // Filter out non-app windows since transitions don't animate those
+                            // (but may still "wait" on them for readiness)
+                            && (win.mActivityRecord != null || win.mIsWallpaper))) {
+                // TODO(b/247005789): set mAnimatingExit somewhere in shell-transitions setup.
                 reason = "animating";
                 win.mAnimatingExit = true;
             } else if (win.mDisplayContent.mWallpaperController.isWallpaperTarget(win)
@@ -3558,16 +3571,9 @@
                 confirm);
     }
 
-    public void setCurrentProfileIds(final int[] currentProfileIds) {
-        synchronized (mGlobalLock) {
-            mCurrentProfileIds = currentProfileIds;
-        }
-    }
-
-    public void setCurrentUser(final int newUserId, final int[] currentProfileIds) {
+    public void setCurrentUser(@UserIdInt int newUserId) {
         synchronized (mGlobalLock) {
             mCurrentUserId = newUserId;
-            mCurrentProfileIds = currentProfileIds;
             mPolicy.setCurrentUserLw(newUserId);
             mKeyguardDisableHandler.setCurrentUser(newUserId);
 
@@ -3590,12 +3596,8 @@
     }
 
     /* Called by WindowState */
-    boolean isCurrentProfile(int userId) {
-        if (userId == mCurrentUserId) return true;
-        for (int i = 0; i < mCurrentProfileIds.length; i++) {
-            if (mCurrentProfileIds[i] == userId) return true;
-        }
-        return false;
+    boolean isUserVisible(@UserIdInt int userId) {
+        return mUmInternal.isUserVisible(userId);
     }
 
     public void enableScreenAfterBoot() {
@@ -9277,46 +9279,4 @@
                         "Unexpected letterbox background type: " + letterboxBackgroundType);
         }
     }
-
-    @Override
-    public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
-            ScreenCapture.ScreenCaptureListener listener) {
-        Slog.d(TAG, "captureDisplay");
-        if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
-            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
-        }
-
-        ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener);
-    }
-
-    @VisibleForTesting
-    ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId,
-            @Nullable ScreenCapture.CaptureArgs captureArgs) {
-        final SurfaceControl displaySurfaceControl;
-        synchronized (mGlobalLock) {
-            DisplayContent displayContent = mRoot.getDisplayContent(displayId);
-            if (displayContent == null) {
-                throw new IllegalArgumentException("Trying to screenshot and invalid display: "
-                        + displayId);
-            }
-
-            displaySurfaceControl = displayContent.getSurfaceControl();
-
-            if (captureArgs == null) {
-                captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
-                        .build();
-            }
-
-            if (captureArgs.mSourceCrop.isEmpty()) {
-                displayContent.getBounds(mTmpRect);
-                mTmpRect.offsetTo(0, 0);
-            } else {
-                mTmpRect.set(captureArgs.mSourceCrop);
-            }
-        }
-
-        return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
-                        .setSourceCrop(mTmpRect)
-                        .build();
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index df7b8bb..1d43d18 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3684,7 +3684,7 @@
         }
 
         return win.showForAllUsers()
-                || mWmService.isCurrentProfile(win.mShowUserId);
+                || mWmService.isUserVisible(win.mShowUserId);
     }
 
     private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp
index 61f2b14..02e5061 100644
--- a/services/core/jni/com_android_server_display_DisplayControl.cpp
+++ b/services/core/jni/com_android_server_display_DisplayControl.cpp
@@ -17,6 +17,7 @@
 #include <android_util_Binder.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
@@ -33,6 +34,27 @@
     SurfaceComposerClient::destroyDisplay(token);
 }
 
+static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject,
+                                   jintArray jHdrTypes) {
+    sp<IBinder> token(ibinderForJavaObject(env, tokenObject));
+    if (token == nullptr || jHdrTypes == nullptr) return;
+
+    ScopedIntArrayRO hdrTypes(env, jHdrTypes);
+    size_t numHdrTypes = hdrTypes.size();
+
+    std::vector<ui::Hdr> hdrTypesVector;
+    hdrTypesVector.reserve(numHdrTypes);
+    for (int i = 0; i < numHdrTypes; i++) {
+        hdrTypesVector.push_back(static_cast<ui::Hdr>(hdrTypes[i]));
+    }
+
+    status_t error = SurfaceComposerClient::overrideHdrTypes(token, hdrTypesVector);
+    if (error != NO_ERROR) {
+        jniThrowExceptionFmt(env, "java/lang/SecurityException",
+                             "ACCESS_SURFACE_FLINGER is missing");
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sDisplayMethods[] = {
@@ -41,6 +63,8 @@
             (void*)nativeCreateDisplay },
     {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V",
             (void*)nativeDestroyDisplay },
+    {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V",
+                (void*)nativeOverrideHdrTypes },
         // clang-format on
 };
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bb6bd4a..42dfee3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -5725,7 +5725,7 @@
         try {
             return setKeyChainGrantInternal(alias, hasGrant, granteeUid, caller.getUserHandle());
         } catch (IllegalArgumentException e) {
-            if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, packageName,
+            if (mInjector.isChangeEnabled(THROW_EXCEPTION_WHEN_KEY_MISSING, callerPackage,
                     caller.getUserId())) {
                 throw e;
             }
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 07b763d..33ac735 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -34,10 +34,15 @@
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
     <uses-permission android:name="android.permission.MANAGE_GAME_ACTIVITY" />
     <uses-permission android:name="android.permission.SET_ALWAYS_FINISH" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
 
     <!-- needed by MasterClearReceiverTest to display a system dialog -->
     <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
 
+    <!-- needed by TrustManagerServiceTest to access LockSettings' secure storage -->
+    <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+
     <application android:testOnly="true"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 9bdc93e..6bf102a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.am;
 
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -46,7 +45,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
@@ -92,7 +90,8 @@
     private final Impl mImpl;
 
     private enum Impl {
-        DEFAULT
+        DEFAULT,
+        MODERN,
     }
 
     private Context mContext;
@@ -116,7 +115,7 @@
 
     @Parameters(name = "impl={0}")
     public static Collection<Object[]> data() {
-        return Arrays.asList(new Object[][] { {Impl.DEFAULT} });
+        return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
     }
 
     public BroadcastQueueTest(Impl impl) {
@@ -152,7 +151,9 @@
             final ApplicationInfo ai = invocation.getArgument(1);
             final ProcessRecord res = makeActiveProcessRecord(ai, processName);
             mHandlerThread.getThreadHandler().post(() -> {
-                mQueue.onApplicationAttachedLocked(res);
+                synchronized (mAms) {
+                    mQueue.onApplicationAttachedLocked(res);
+                }
             });
             return res;
         }).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
@@ -177,6 +178,9 @@
             mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
                     constants, emptySkipPolicy, emptyHistory, false,
                     ProcessList.SCHED_GROUP_DEFAULT);
+        } else if (mImpl == Impl.MODERN) {
+            mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+                    constants, emptySkipPolicy, emptyHistory);
         } else {
             throw new UnsupportedOperationException();
         }
@@ -230,8 +234,10 @@
             Log.v(TAG, "Intercepting scheduleReceiver() for "
                     + Arrays.toString(invocation.getArguments()));
             mHandlerThread.getThreadHandler().post(() -> {
-                mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
-                        null, null, false, false);
+                synchronized (mAms) {
+                    mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
+                            null, null, false, false);
+                }
             });
             return null;
         }).when(thread).scheduleReceiver(any(), any(), any(), anyInt(), any(), any(), anyBoolean(),
@@ -240,10 +246,15 @@
         doAnswer((invocation) -> {
             Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
                     + Arrays.toString(invocation.getArguments()));
-            mHandlerThread.getThreadHandler().post(() -> {
-                mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null, null,
-                        false, false);
-            });
+            final boolean ordered = invocation.getArgument(5);
+            if (ordered) {
+                mHandlerThread.getThreadHandler().post(() -> {
+                    synchronized (mAms) {
+                        mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
+                                null, null, false, false);
+                    }
+                });
+            }
             return null;
         }).when(thread).scheduleRegisteredReceiver(any(), any(), anyInt(), any(), any(),
                 anyBoolean(), anyBoolean(), anyInt(), anyInt());
@@ -302,6 +313,12 @@
         };
     }
 
+    private void enqueueBroadcast(BroadcastRecord r) {
+        synchronized (mAms) {
+            mQueue.enqueueBroadcastLocked(r);
+        }
+    }
+
     private void waitForIdle() throws Exception {
         mQueue.waitForIdle(null);
     }
@@ -349,7 +366,7 @@
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
         final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(intent, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
 
         waitForIdle();
@@ -368,12 +385,12 @@
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
 
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
                         makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
 
         waitForIdle();
@@ -394,12 +411,12 @@
         // the second time it should already be running
 
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
                         makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
 
         waitForIdle();
@@ -407,7 +424,6 @@
                 getUidForPackage(PACKAGE_GREEN));
         final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
                 getUidForPackage(PACKAGE_BLUE));
-        assertTrue(receiverBlueApp.getPid() > receiverGreenApp.getPid());
         verifyScheduleReceiver(receiverGreenApp, timezone);
         verifyScheduleReceiver(receiverGreenApp, airplane);
         verifyScheduleReceiver(receiverBlueApp, timezone);
@@ -423,7 +439,7 @@
         final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
 
         final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(intent, callerApp,
                 List.of(makeRegisteredReceiver(receiverApp))));
 
         waitForIdle();
@@ -442,12 +458,12 @@
         final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
 
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
                 List.of(makeRegisteredReceiver(receiverGreenApp),
                         makeRegisteredReceiver(receiverBlueApp))));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                 List.of(makeRegisteredReceiver(receiverBlueApp))));
 
         waitForIdle();
@@ -468,14 +484,14 @@
         final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
 
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(timezone, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
                         makeRegisteredReceiver(receiverGreenApp),
                         makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
                         makeRegisteredReceiver(receiverYellowApp))));
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-        mQueue.enqueueBroadcastLocked(makeBroadcastRecord(airplane, callerApp,
+        enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
                 List.of(makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
 
         waitForIdle();
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
new file mode 100644
index 0000000..1a5d496
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -0,0 +1,235 @@
+/*
+ * 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.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+import android.util.FloatProperty;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.display.RampAnimator.DualRampAnimator;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerController2Test {
+    private static final String UNIQUE_DISPLAY_ID = "unique_id_test123";
+    private static final int DISPLAY_ID = 42;
+
+    private OffsettableClock mClock;
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private DisplayPowerController2.Injector mInjector;
+    private Context mContextSpy;
+
+    @Mock
+    private DisplayPowerCallbacks mDisplayPowerCallbacksMock;
+    @Mock
+    private SensorManager mSensorManagerMock;
+    @Mock
+    private DisplayBlanker mDisplayBlankerMock;
+    @Mock
+    private LogicalDisplay mLogicalDisplayMock;
+    @Mock
+    private DisplayDevice mDisplayDeviceMock;
+    @Mock
+    private BrightnessTracker mBrightnessTrackerMock;
+    @Mock
+    private BrightnessSetting mBrightnessSettingMock;
+    @Mock
+    private WindowManagerPolicy mWindowManagerPolicyMock;
+    @Mock
+    private PowerManager mPowerManagerMock;
+    @Mock
+    private Resources mResourcesMock;
+    @Mock
+    private DisplayDeviceConfig mDisplayDeviceConfigMock;
+    @Mock
+    private DisplayPowerState mDisplayPowerStateMock;
+    @Mock
+    private DualRampAnimator<DisplayPowerState> mDualRampAnimatorMock;
+
+    @Captor
+    private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
+        mHandler = new Handler(mTestLooper.getLooper());
+        mInjector = new DisplayPowerController2.Injector() {
+            @Override
+            DisplayPowerController2.Clock getClock() {
+                return mClock::now;
+            }
+
+            @Override
+            DisplayPowerState getDisplayPowerState(DisplayBlanker blanker, ColorFade colorFade,
+                    int displayId, int displayState) {
+                return mDisplayPowerStateMock;
+            }
+
+            @Override
+            DualRampAnimator<DisplayPowerState> getDualRampAnimator(DisplayPowerState dps,
+                    FloatProperty<DisplayPowerState> firstProperty,
+                    FloatProperty<DisplayPowerState> secondProperty) {
+                return mDualRampAnimatorMock;
+            }
+        };
+
+        addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+
+        when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
+        when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+    }
+
+    @Test
+    public void testReleaseProxSuspendBlockersOnExit() throws Exception {
+        setUpDisplay(DISPLAY_ID, UNIQUE_DISPLAY_ID);
+
+        Sensor proxSensor = setUpProxSensor();
+
+        DisplayPowerController2 dpc = new DisplayPowerController2(
+                mContextSpy, mInjector, mDisplayPowerCallbacksMock, mHandler,
+                mSensorManagerMock, mDisplayBlankerMock, mLogicalDisplayMock,
+                mBrightnessTrackerMock, mBrightnessSettingMock, () -> {
+        });
+
+        when(mDisplayPowerStateMock.getScreenState()).thenReturn(Display.STATE_ON);
+        // send a display power request
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        dpr.useProximitySensor = true;
+        dpc.requestPowerState(dpr, false /* waitForNegativeProximity */);
+
+        // Run updatePowerState to start listener for the prox sensor
+        advanceTime(1);
+
+        SensorEventListener listener = getSensorEventListener(proxSensor);
+        assertNotNull(listener);
+
+        listener.onSensorChanged(TestUtils.createSensorEvent(proxSensor, 5 /* lux */));
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).acquireSuspendBlocker(
+                dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+
+        dpc.stop();
+        advanceTime(1);
+
+        // two times, one for unfinished business and one for proximity
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                dpc.getSuspendBlockerUnfinishedBusinessId(DISPLAY_ID));
+        verify(mDisplayPowerCallbacksMock).releaseSuspendBlocker(
+                dpc.getSuspendBlockerProxDebounceId(DISPLAY_ID));
+    }
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    private void advanceTime(long timeMs) {
+        mClock.fastForward(timeMs);
+        mTestLooper.dispatchAll();
+    }
+
+    private Sensor setUpProxSensor() throws Exception {
+        Sensor proxSensor = TestUtils.createSensor(
+                Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY);
+        when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL)))
+                .thenReturn(List.of(proxSensor));
+        return proxSensor;
+    }
+
+    private SensorEventListener getSensorEventListener(Sensor sensor) {
+        verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(),
+                eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class));
+        return mSensorEventListenerCaptor.getValue();
+    }
+
+    private void setUpDisplay(int displayId, String uniqueId) {
+        DisplayInfo info = new DisplayInfo();
+        DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo();
+
+        when(mLogicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId);
+        when(mLogicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(mDisplayDeviceMock);
+        when(mLogicalDisplayMock.getDisplayInfoLocked()).thenReturn(info);
+        when(mLogicalDisplayMock.isEnabled()).thenReturn(true);
+        when(mLogicalDisplayMock.getPhase()).thenReturn(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+        when(mDisplayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo);
+        when(mDisplayDeviceMock.getUniqueId()).thenReturn(uniqueId);
+        when(mDisplayDeviceMock.getDisplayDeviceConfig()).thenReturn(mDisplayDeviceConfigMock);
+        when(mDisplayDeviceConfigMock.getProximitySensor()).thenReturn(
+                new DisplayDeviceConfig.SensorData() {
+                    {
+                        type = Sensor.STRING_TYPE_PROXIMITY;
+                        name = null;
+                    }
+                });
+        when(mDisplayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index bc9e9bb..941a3a4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -16,14 +16,18 @@
 
 package com.android.server.display.color;
 
+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.junit.Assert.assertArrayEquals;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.Binder;
 import android.os.IBinder;
 import android.view.SurfaceControl;
@@ -50,6 +54,8 @@
     private Context mMockedContext;
     @Mock
     private Resources mMockedResources;
+    @Mock
+    private DisplayManagerInternal mDisplayManagerInternal;
 
     private MockitoSession mSession;
     private Resources mResources;
@@ -81,7 +87,6 @@
         doReturn(mMockedResources).when(mMockedContext).getResources();
 
         mDisplayToken = new Binder();
-        doReturn(mDisplayToken).when(() -> SurfaceControl.getInternalDisplayToken());
     }
 
     @After
@@ -114,8 +119,8 @@
         displayPrimaries.white.X = 0.950456f;
         displayPrimaries.white.Y = 1.000000f;
         displayPrimaries.white.Z = 1.089058f;
-        doReturn(displayPrimaries)
-            .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
 
         setUpTintController();
         assertWithMessage("Setup with valid SurfaceControl failed")
@@ -134,8 +139,8 @@
         displayPrimaries.green = new CieXyz();
         displayPrimaries.blue = new CieXyz();
         displayPrimaries.white = new CieXyz();
-        doReturn(displayPrimaries)
-            .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
 
         setUpTintController();
         assertWithMessage("Setup with invalid SurfaceControl succeeded")
@@ -154,7 +159,7 @@
             .when(mMockedResources)
             .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
         // Make SurfaceControl setup fail
-        doReturn(null).when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
 
         setUpTintController();
         assertWithMessage("Setup with valid Resources failed")
@@ -178,7 +183,7 @@
             .when(mMockedResources)
             .getStringArray(R.array.config_displayWhiteBalanceDisplayPrimaries);
         // Make SurfaceControl setup fail
-        doReturn(null).when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY)).thenReturn(null);
 
         setUpTintController();
         assertWithMessage("Setup with invalid Resources succeeded")
@@ -208,8 +213,8 @@
         displayPrimaries.white.X = 0.950456f;
         displayPrimaries.white.Y = 1.000000f;
         displayPrimaries.white.Z = 1.089058f;
-        doReturn(displayPrimaries)
-                .when(() -> SurfaceControl.getDisplayNativePrimaries(mDisplayToken));
+        when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+                .thenReturn(displayPrimaries);
 
         setUpTintController();
         assertWithMessage("Setup with valid SurfaceControl failed")
@@ -234,7 +239,8 @@
     }
 
     private void setUpTintController() {
-        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController();
+        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
+                mDisplayManagerInternal);
         mDisplayWhiteBalanceTintController.setUp(mMockedContext, true);
         mDisplayWhiteBalanceTintController.setActivated(true);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
new file mode 100644
index 0000000..c2768d51
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakePackageResetHelper.java
@@ -0,0 +1,38 @@
+/*
+ * 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.location.injector;
+
+/** Version of PackageResetHelper for testing. */
+public class FakePackageResetHelper extends PackageResetHelper {
+
+    public FakePackageResetHelper() {}
+
+    @Override
+    protected void onRegister() {}
+
+    @Override
+    protected void onUnregister() {}
+
+    public boolean isResetableForPackage(String packageName) {
+        return queryResetableForPackage(packageName);
+    }
+
+    public void reset(String packageName) {
+        notifyPackageReset(packageName);
+    }
+}
+
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
index 02cacb7..ca73091 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java
@@ -35,6 +35,7 @@
     private final FakeDeviceIdleHelper mDeviceIdleHelper;
     private final FakeEmergencyHelper mEmergencyHelper;
     private final LocationUsageLogger mLocationUsageLogger;
+    private final FakePackageResetHelper mPackageResetHelper;
 
     public TestInjector(Context context) {
         mUserInfoHelper = new FakeUserInfoHelper();
@@ -50,6 +51,7 @@
         mDeviceIdleHelper = new FakeDeviceIdleHelper();
         mEmergencyHelper = new FakeEmergencyHelper();
         mLocationUsageLogger = new LocationUsageLogger();
+        mPackageResetHelper = new FakePackageResetHelper();
     }
 
     @Override
@@ -116,4 +118,9 @@
     public LocationUsageLogger getLocationUsageLogger() {
         return mLocationUsageLogger;
     }
+
+    @Override
+    public FakePackageResetHelper getPackageResetHelper() {
+        return mPackageResetHelper;
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 0ac1443..20e4e80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -1218,6 +1218,44 @@
         assertThat(mProvider.getRequest().isActive()).isFalse();
     }
 
+    @Test
+    public void testQueryPackageReset() {
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+
+        ILocationListener listener1 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        ILocationListener listener2 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mManager.unregisterLocationRequest(listener1);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mManager.unregisterLocationRequest(listener2);
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+    }
+
+    @Test
+    public void testPackageReset() {
+        ILocationListener listener1 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener1);
+        ILocationListener listener2 = createMockLocationListener();
+        mManager.registerLocationRequest(new LocationRequest.Builder(0).setWorkSource(
+                WORK_SOURCE).build(), IDENTITY, PERMISSION_FINE, listener2);
+
+        assertThat(mProvider.getRequest().isActive()).isTrue();
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isTrue();
+
+        mInjector.getPackageResetHelper().reset("mypackage");
+        assertThat(mProvider.getRequest().isActive()).isFalse();
+        assertThat(mInjector.getPackageResetHelper().isResetableForPackage("mypackage")).isFalse();
+    }
+
     private ILocationListener createMockLocationListener() {
         return spy(new ILocationListener.Stub() {
             @Override
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index bb5b1d8..cc57b9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -62,10 +62,10 @@
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.dex.DexManager
 import com.android.server.pm.parsing.PackageParser2
-import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.parsing.pkg.ParsedPackage
 import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.parsing.ParsingPackage
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
@@ -77,14 +77,6 @@
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
 import com.android.server.utils.WatchedArrayMap
-import libcore.util.HexEncoding
-import org.junit.Assert
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import org.mockito.AdditionalMatchers.or
-import org.mockito.Mockito
-import org.mockito.quality.Strictness
 import java.io.File
 import java.io.IOException
 import java.nio.file.Files
@@ -93,6 +85,14 @@
 import java.util.Arrays
 import java.util.Random
 import java.util.concurrent.FutureTask
+import libcore.util.HexEncoding
+import org.junit.Assert
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.AdditionalMatchers.or
+import org.mockito.Mockito
+import org.mockito.quality.Strictness
 
 /**
  * A utility for mocking behavior of the system and dependencies when testing PackageManagerService
@@ -522,7 +522,7 @@
         whenever(mocks.packageParser.parsePackage(
                 or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
         whenever(mocks.packageParser.parsePackage(
-                or(eq(path), eq(basePath)), anyInt(), anyBoolean(), any())) { parsedPackage }
+                or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
         return parsedPackage
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index 987192d..da929af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -23,6 +23,7 @@
 import android.util.Log
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.testutils.whenever
+import java.io.File
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
 import org.hamcrest.Matchers.notNullValue
@@ -33,13 +34,11 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.argThat
 import org.mockito.Mockito
 import org.mockito.Mockito.verify
-import java.io.File
 
 @RunWith(JUnit4::class)
 class PackageManagerServiceBootTest {
@@ -120,8 +119,7 @@
         whenever(rule.mocks().packageParser.parsePackage(
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
-                anyBoolean(),
-                any()))
+                anyBoolean()))
                 .thenThrow(PackageManagerException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
new file mode 100644
index 0000000..33870f1
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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.trust;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.service.trust.TrustAgentService;
+import android.testing.TestableContext;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+
+import com.google.android.collect.Lists;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class TrustManagerServiceTest {
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public final MockContext mMockContext = new MockContext(
+            ApplicationProvider.getApplicationContext());
+
+    private static final String URI_SCHEME_PACKAGE = "package";
+    private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
+    private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
+    private final TrustManagerService mService = new TrustManagerService(mMockContext);
+
+    @Mock
+    private PackageManager mPackageManagerMock;
+
+    @Before
+    public void setUp() {
+        resetTrustAgentLockSettings();
+        LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+        ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
+            @Override
+            public boolean matches(Intent argument) {
+                return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
+            }
+        };
+        when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+                anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
+        when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+                PackageManager.PERMISSION_GRANTED);
+        mMockContext.setMockPackageManager(mPackageManagerMock);
+    }
+
+    @After
+    public void tearDown() {
+        resetTrustAgentLockSettings();
+        LocalServices.removeServiceForTest(SystemServiceManager.class);
+    }
+
+    @Test
+    public void firstBootCompleted_systemTrustAgentsEnabled() {
+        ComponentName systemTrustAgent1 = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        ComponentName systemTrustAgent2 = ComponentName.unflattenFromString(
+                "com.android/.AnotherSystemTrustAgent");
+        ComponentName userTrustAgent1 = ComponentName.unflattenFromString(
+                "com.user/.UserTrustAgent");
+        ComponentName userTrustAgent2 = ComponentName.unflattenFromString(
+                "com.user/.AnotherUserTrustAgent");
+        addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true);
+        addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
+        addTrustAgent(userTrustAgent1, /* isSystemApp= */ false);
+        addTrustAgent(userTrustAgent2, /* isSystemApp= */ false);
+
+        bootService();
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                systemTrustAgent1, systemTrustAgent2);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+    }
+
+    @Test
+    public void firstBootCompleted_defaultTrustAgentEnabled() {
+        ComponentName systemTrustAgent = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        ComponentName defaultTrustAgent = ComponentName.unflattenFromString(
+                "com.user/.DefaultTrustAgent");
+        addTrustAgent(systemTrustAgent, /* isSystemApp= */ true);
+        addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false);
+        mMockContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultTrustAgent,
+                defaultTrustAgent.flattenToString());
+
+        bootService();
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                defaultTrustAgent);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                systemTrustAgent, defaultTrustAgent);
+    }
+
+    @Test
+    public void serviceBooted_knownAgentsNotSet_enabledAgentsNotUpdated() {
+        ComponentName trustAgent1 = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        ComponentName trustAgent2 = ComponentName.unflattenFromString(
+                "com.android/.AnotherSystemTrustAgent");
+        initializeEnabledAgents(trustAgent1);
+        addTrustAgent(trustAgent1, /* isSystemApp= */ true);
+        addTrustAgent(trustAgent2, /* isSystemApp= */ true);
+
+        bootService();
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                trustAgent1);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                trustAgent1, trustAgent2);
+    }
+
+    @Test
+    public void serviceBooted_knownAgentsSet_enabledAgentsUpdated() {
+        ComponentName trustAgent1 = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        ComponentName trustAgent2 = ComponentName.unflattenFromString(
+                "com.android/.AnotherSystemTrustAgent");
+        initializeEnabledAgents(trustAgent1);
+        initializeKnownAgents(trustAgent1);
+        addTrustAgent(trustAgent1, /* isSystemApp= */ true);
+        addTrustAgent(trustAgent2, /* isSystemApp= */ true);
+
+        bootService();
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                trustAgent1, trustAgent2);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                trustAgent1, trustAgent2);
+    }
+
+    @Test
+    public void newSystemTrustAgent_setToEnabledAndKnown() {
+        bootService();
+        ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
+
+        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                newAgentComponentName);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                newAgentComponentName);
+    }
+
+    @Test
+    public void newSystemTrustAgent_notEnabledWhenDefaultAgentIsSet() {
+        ComponentName defaultTrustAgent = ComponentName.unflattenFromString(
+                "com.user/.DefaultTrustAgent");
+        addTrustAgent(defaultTrustAgent, /* isSystemApp= */ false);
+        mMockContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultTrustAgent,
+                defaultTrustAgent.flattenToString());
+        bootService();
+        ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        addTrustAgent(newAgentComponentName, /* isSystemApp= */ true);
+
+        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                defaultTrustAgent);
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                defaultTrustAgent, newAgentComponentName);
+    }
+
+    @Test
+    public void newNonSystemTrustAgent_notEnabledButMarkedAsKnown() {
+        bootService();
+        ComponentName newAgentComponentName = ComponentName.unflattenFromString(
+                "com.user/.UserTrustAgent");
+        addTrustAgent(newAgentComponentName, /* isSystemApp= */ false);
+
+        mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
+        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
+                newAgentComponentName);
+    }
+
+    @Test
+    public void existingTrustAgentChanged_notEnabled() {
+        ComponentName systemTrustAgent1 = ComponentName.unflattenFromString(
+                "com.android/.SystemTrustAgent");
+        ComponentName systemTrustAgent2 = ComponentName.unflattenFromString(
+                "com.android/.AnotherSystemTrustAgent");
+        addTrustAgent(systemTrustAgent1, /* isSystemApp= */ true);
+        addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
+        bootService();
+        // Simulate user turning off systemTrustAgent2
+        mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
+                TEST_USER_ID);
+
+        mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
+
+        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
+                systemTrustAgent1);
+    }
+
+    private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        if (isSystemApp) {
+            applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+        }
+
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.packageName = agentComponentName.getPackageName();
+        serviceInfo.name = agentComponentName.getClassName();
+        serviceInfo.applicationInfo = applicationInfo;
+
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = serviceInfo;
+        mTrustAgentResolveInfoList.add(resolveInfo);
+    }
+
+    private void initializeEnabledAgents(ComponentName... enabledAgents) {
+        mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+    }
+
+    private void initializeKnownAgents(ComponentName... knownAgents) {
+        mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+    }
+
+    private void bootService() {
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+    }
+
+    private void resetTrustAgentLockSettings() {
+        mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
+        mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+    }
+
+    /** A mock Context that allows the test process to send protected broadcasts. */
+    private static final class MockContext extends TestableContext {
+
+        private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
+                new ArrayList<>();
+
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        @Nullable
+        public Intent registerReceiverAsUser(BroadcastReceiver receiver,
+                UserHandle user, IntentFilter filter, @Nullable String broadcastPermission,
+                @Nullable Handler scheduler) {
+
+            if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
+                mPackageChangedBroadcastReceivers.add(receiver);
+            }
+            return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+                    scheduler);
+        }
+
+        void sendPackageChangedBroadcast(ComponentName changedComponent) {
+            Intent intent = new Intent(
+                    Intent.ACTION_PACKAGE_CHANGED,
+                    Uri.fromParts(URI_SCHEME_PACKAGE,
+                            changedComponent.getPackageName(), /* fragment= */ null))
+                    .putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                            new String[]{changedComponent.getClassName()})
+                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID)
+                    .putExtra(Intent.EXTRA_UID, UserHandle.of(TEST_USER_ID).getUid(1234));
+            for (BroadcastReceiver receiver : mPackageChangedBroadcastReceivers) {
+                receiver.onReceive(this, intent);
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index 3f4148b..a45144e 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -588,6 +588,157 @@
         });
     }
 
+    @Test
+    public void requestBaseStateOverride() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+        flushHandler();
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestBaseStateOverride(token,
+                OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+        // Flush the handler twice. The first flush ensures the request is added and the policy is
+        // notified, while the second flush ensures the callback is notified once the change is
+        // committed.
+        flushHandler(2 /* count */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        assertNotNull(callback.getLastNotifiedInfo());
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mService.getBinderService().cancelBaseStateOverride();
+        flushHandler();
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set back to the requested state once the override is cleared.
+        assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+        assertFalse(mService.getOverrideBaseState().isPresent());
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                DEFAULT_DEVICE_STATE.getIdentifier());
+
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                DEFAULT_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                DEFAULT_DEVICE_STATE.getIdentifier());
+    }
+
+    @Test
+    public void requestBaseStateOverride_cancelledByBaseStateUpdate() throws RemoteException {
+        final DeviceState testDeviceState = new DeviceState(2, "TEST", 0);
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+        mProvider.notifySupportedDeviceStates(
+                new DeviceState[]{DEFAULT_DEVICE_STATE, OTHER_DEVICE_STATE, testDeviceState });
+        flushHandler();
+
+        final IBinder token = new Binder();
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_UNKNOWN);
+
+        mService.getBinderService().requestBaseStateOverride(token,
+                OTHER_DEVICE_STATE.getIdentifier(),
+                0 /* flags */);
+        // Flush the handler twice. The first flush ensures the request is added and the policy is
+        // notified, while the second flush ensures the callback is notified once the change is
+        // committed.
+        flushHandler(2 /* count */);
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_ACTIVE);
+        // Committed state changes as there is a requested override.
+        assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mSysPropSetter.getValue(),
+                OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
+        assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+        assertEquals(mService.getOverrideBaseState().get(), OTHER_DEVICE_STATE);
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        assertNotNull(callback.getLastNotifiedInfo());
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                OTHER_DEVICE_STATE.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                OTHER_DEVICE_STATE.getIdentifier());
+
+        mProvider.setState(testDeviceState.getIdentifier());
+        flushHandler();
+
+        assertEquals(callback.getLastNotifiedStatus(token),
+                TestDeviceStateManagerCallback.STATUS_CANCELED);
+        // Committed state is set to the new base state once the override is cleared.
+        assertEquals(mService.getCommittedState(), Optional.of(testDeviceState));
+        assertEquals(mSysPropSetter.getValue(),
+                testDeviceState.getIdentifier() + ":" + testDeviceState.getName());
+        assertEquals(mService.getBaseState(), Optional.of(testDeviceState));
+        assertFalse(mService.getOverrideBaseState().isPresent());
+        assertFalse(mService.getOverrideState().isPresent());
+        assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
+                testDeviceState.getIdentifier());
+
+        assertEquals(callback.getLastNotifiedInfo().baseState,
+                testDeviceState.getIdentifier());
+        assertEquals(callback.getLastNotifiedInfo().currentState,
+                testDeviceState.getIdentifier());
+    }
+
+    @Test
+    public void requestBaseState_unsupportedState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token,
+                    UNSUPPORTED_DEVICE_STATE.getIdentifier(), 0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestBaseState_invalidState() throws RemoteException {
+        TestDeviceStateManagerCallback callback = new TestDeviceStateManagerCallback();
+        mService.getBinderService().registerCallback(callback);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token, INVALID_DEVICE_STATE,
+                    0 /* flags */);
+        });
+    }
+
+    @Test
+    public void requestBaseState_beforeRegisteringCallback() {
+        assertThrows(IllegalStateException.class, () -> {
+            final IBinder token = new Binder();
+            mService.getBinderService().requestBaseStateOverride(token,
+                    DEFAULT_DEVICE_STATE.getIdentifier(),
+                    0 /* flags */);
+        });
+    }
+
     private static void assertArrayEquals(int[] expected, int[] actual) {
         Assert.assertTrue(Arrays.equals(expected, actual));
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index 2297c91..430504c 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.devicestate;
 
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
+import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_ACTIVE;
 import static com.android.server.devicestate.OverrideRequestController.STATUS_CANCELED;
 
@@ -57,7 +59,7 @@
     @Test
     public void addRequest() {
         OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
         mController.addRequest(request);
@@ -67,14 +69,14 @@
     @Test
     public void addRequest_cancelExistingRequestThroughNewRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
         mController.addRequest(secondRequest);
@@ -85,7 +87,7 @@
     @Test
     public void addRequest_cancelActiveRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
 
@@ -97,30 +99,90 @@
     }
 
     @Test
-    public void handleBaseStateChanged() {
-        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */,
-                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */);
+    public void addBaseStateRequest() {
+        OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(request));
 
-        mController.addRequest(firstRequest);
+        mController.addBaseStateRequest(request);
+        assertEquals(mStatusListener.getLastStatus(request).intValue(), STATUS_ACTIVE);
+    }
+
+    @Test
+    public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(firstRequest));
+
+        mController.addBaseStateRequest(firstRequest);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+        assertNull(mStatusListener.getLastStatus(secondRequest));
+
+        mController.addBaseStateRequest(secondRequest);
+        assertEquals(mStatusListener.getLastStatus(secondRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+    }
+
+    @Test
+    public void addBaseStateRequest_cancelActiveBaseStateRequest() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+        mController.addBaseStateRequest(firstRequest);
 
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleBaseStateChanged();
+        mController.cancelBaseStateOverrideRequest();
 
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
+    public void handleBaseStateChanged() {
+        OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */,
+                DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
+                OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                0 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+
+        mController.addRequest(firstRequest);
+
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+
+        mController.addBaseStateRequest(baseStateRequest);
+
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+        mController.handleBaseStateChanged(1);
+
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
+    }
+
+    @Test
     public void handleProcessDied() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
         mController.handleProcessDied(0);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
@@ -128,13 +190,20 @@
         mController.setStickyRequestsAllowed(true);
 
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                0 /* requestedState */, 0 /* flags */);
+                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
         mController.handleProcessDied(0);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
 
         mController.cancelStickyRequest();
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
@@ -143,22 +212,31 @@
     @Test
     public void handleNewSupportedStates() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+
+        OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
+                1 /* requestedState */,
+                0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{ 0, 1 });
-        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        mController.addBaseStateRequest(baseStateRequest);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
 
-        mController.handleNewSupportedStates(new int[]{ 0 });
+        mController.handleNewSupportedStates(new int[]{0, 1});
+        assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_ACTIVE);
+
+        mController.handleNewSupportedStates(new int[]{0});
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_CANCELED);
+        assertEquals(mStatusListener.getLastStatus(baseStateRequest).intValue(), STATUS_CANCELED);
     }
 
     @Test
     public void cancelOverrideRequestsTest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */,
-                1 /* requestedState */, 0 /* flags */);
+                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 4ef156e..e0bef1a 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -18,21 +18,26 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import androidx.test.InstrumentationRegistry;
+import static org.mockito.Mockito.mock;
 
-import java.lang.System;
-import java.util.Arrays;
+import android.hardware.display.DisplayManagerInternal;
+
+import androidx.test.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class DisplayWhiteBalanceTintControllerTest {
 
     private DisplayWhiteBalanceTintController mDisplayWhiteBalanceTintController;
 
     @Before
     public void setUp() {
-        mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController();
+        DisplayManagerInternal displayManagerInternal = mock(DisplayManagerInternal.class);
+        mDisplayWhiteBalanceTintController =
+                new DisplayWhiteBalanceTintController(displayManagerInternal);
         mDisplayWhiteBalanceTintController.setUp(InstrumentationRegistry.getContext(), true);
         mDisplayWhiteBalanceTintController.setActivated(true);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
index e04edc6..96707fd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -216,6 +216,25 @@
         assertThat(mUserManagerService.isUserSwitcherEnabled(userId)).isTrue();
     }
 
+    @Test
+    public void assertIsUserSwitcherEnabled()  throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+        setMaxSupportedUsers(8);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isTrue();
+
+        setUserSwitch(false);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
+
+        setUserSwitch(true);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isTrue();
+
+        mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, userId);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(false, userId)).isFalse();
+
+        mUserManagerService.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userId);
+        setMaxSupportedUsers(1);
+        assertThat(mUserManagerService.isUserSwitcherEnabled(true, userId)).isFalse();
+    }
 
     @Test
     public void assertIsUserSwitcherEnabledOnShowMultiuserUI()  throws Exception {
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 107bbe1..2918365 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -79,10 +79,7 @@
         <activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true" />
-        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
-                  android:theme="@style/WhiteBackgroundTheme"
-                  android:turnScreenOn="true"
-                  android:showWhenLocked="true"/>
+        <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
         <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
 
         <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 8cd8e9b..0b58428 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,10 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.WindowInsets.Type.displayCutout;
-import static android.view.WindowInsets.Type.statusBars;
-
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertNotNull;
@@ -27,31 +23,20 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
-import android.graphics.Insets;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.hardware.DataSpace;
-import android.hardware.HardwareBuffer;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.platform.test.annotations.Presubmit;
-import android.view.IWindowManager;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
-import android.view.cts.surfacevalidator.BitmapPixelChecker;
-import android.view.cts.surfacevalidator.PixelColor;
-import android.view.cts.surfacevalidator.SaveBitmapHelper;
+import android.view.WindowManager;
 import android.window.ScreenCapture;
-import android.window.ScreenCapture.SyncScreenCaptureListener;
 
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
@@ -60,7 +45,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TestName;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -76,8 +60,6 @@
     private static final int BUFFER_HEIGHT = 100;
 
     private final Instrumentation mInstrumentation = getInstrumentation();
-    @Rule
-    public TestName mTestName = new TestName();
 
     @Rule
     public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -113,8 +95,8 @@
         buffer.unlockCanvasAndPost(canvas);
 
         t.show(secureSC)
-                .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
-                .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
+                .setBuffer(secureSC, buffer)
+                .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
                 .apply(true);
 
         ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -130,69 +112,15 @@
         Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
         screenshot.recycle();
 
-        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
-        Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
-        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
-        int sizeOfBitmap = bounds.width() * bounds.height();
+        int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
+                new PixelColor(PixelColor.RED));
+        long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
         boolean success = numMatchingPixels == sizeOfBitmap;
         swBitmap.recycle();
 
         assertTrue(success);
     }
 
-    @Test
-    public void testCaptureDisplay() throws RemoteException {
-        IWindowManager windowManager = IWindowManager.Stub.asInterface(
-                ServiceManager.getService(Context.WINDOW_SERVICE));
-        SurfaceControl sc = new SurfaceControl.Builder()
-                .setName("Layer")
-                .setCallsite("testCaptureDisplay")
-                .build();
-
-        SurfaceControl.Transaction t = mActivity.addChildSc(sc);
-        mInstrumentation.waitForIdleSync();
-
-        GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
-                PixelFormat.RGBA_8888,
-                GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
-                        | GraphicBuffer.USAGE_SW_WRITE_RARELY);
-
-        Canvas canvas = buffer.lockCanvas();
-        canvas.drawColor(Color.RED);
-        buffer.unlockCanvasAndPost(canvas);
-
-        Point point = mActivity.getPositionBelowStatusBar();
-        t.show(sc)
-                .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
-                .setDataSpace(sc, DataSpace.DATASPACE_SRGB)
-                .setPosition(sc, point.x, point.y)
-                .apply(true);
-
-        SyncScreenCaptureListener listener = new SyncScreenCaptureListener();
-        windowManager.captureDisplay(DEFAULT_DISPLAY, null, listener.getScreenCaptureListener());
-        ScreenCapture.ScreenshotHardwareBuffer hardwareBuffer = listener.waitForScreenshot();
-        assertNotNull(hardwareBuffer);
-
-        Bitmap screenshot = hardwareBuffer.asBitmap();
-        assertNotNull(screenshot);
-
-        Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
-        screenshot.recycle();
-
-        BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
-        Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
-        int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
-        int pixelMatchSize = bounds.width() * bounds.height();
-        boolean success = numMatchingPixels == pixelMatchSize;
-
-        if (!success) {
-            SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
-        }
-        swBitmap.recycle();
-        assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
-                success);
-    }
-
     public static class ScreenshotActivity extends Activity {
         private static final long WAIT_TIMEOUT_S = 5;
         private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -202,6 +130,7 @@
             super.onCreate(savedInstanceState);
             getWindow().getDecorView().setPointerIcon(
                     PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
 
         SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -219,14 +148,88 @@
             }
             return t;
         }
+    }
 
-        public Point getPositionBelowStatusBar() {
-            Insets statusBarInsets = getWindow()
-                    .getDecorView()
-                    .getRootWindowInsets()
-                    .getInsets(statusBars() | displayCutout());
+    public abstract static class PixelChecker {
+        static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
+            int numMatchingPixels = 0;
+            for (int x = 0; x < bitmap.getWidth(); x++) {
+                for (int y = 0; y < bitmap.getHeight(); y++) {
+                    int color = bitmap.getPixel(x, y);
+                    if (matchesColor(pixelColor, color)) {
+                        numMatchingPixels++;
+                    }
+                }
+            }
+            return numMatchingPixels;
+        }
 
-            return new Point(statusBarInsets.left, statusBarInsets.top);
+        static boolean matchesColor(PixelColor expectedColor, int color) {
+            final float red = Color.red(color);
+            final float green = Color.green(color);
+            final float blue = Color.blue(color);
+            final float alpha = Color.alpha(color);
+
+            return alpha <= expectedColor.mMaxAlpha
+                    && alpha >= expectedColor.mMinAlpha
+                    && red <= expectedColor.mMaxRed
+                    && red >= expectedColor.mMinRed
+                    && green <= expectedColor.mMaxGreen
+                    && green >= expectedColor.mMinGreen
+                    && blue <= expectedColor.mMaxBlue
+                    && blue >= expectedColor.mMinBlue;
+        }
+    }
+
+    public static class PixelColor {
+        public static final int BLACK = 0xFF000000;
+        public static final int RED = 0xFF0000FF;
+        public static final int GREEN = 0xFF00FF00;
+        public static final int BLUE = 0xFFFF0000;
+        public static final int YELLOW = 0xFF00FFFF;
+        public static final int MAGENTA = 0xFFFF00FF;
+        public static final int WHITE = 0xFFFFFFFF;
+
+        public static final int TRANSPARENT_RED = 0x7F0000FF;
+        public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+        public static final int TRANSPARENT = 0x00000000;
+
+        // Default to black
+        public short mMinAlpha;
+        public short mMaxAlpha;
+        public short mMinRed;
+        public short mMaxRed;
+        public short mMinBlue;
+        public short mMaxBlue;
+        public short mMinGreen;
+        public short mMaxGreen;
+
+        public PixelColor(int color) {
+            short alpha = (short) ((color >> 24) & 0xFF);
+            short blue = (short) ((color >> 16) & 0xFF);
+            short green = (short) ((color >> 8) & 0xFF);
+            short red = (short) (color & 0xFF);
+
+            mMinAlpha = (short) getMinValue(alpha);
+            mMaxAlpha = (short) getMaxValue(alpha);
+            mMinRed = (short) getMinValue(red);
+            mMaxRed = (short) getMaxValue(red);
+            mMinBlue = (short) getMinValue(blue);
+            mMaxBlue = (short) getMaxValue(blue);
+            mMinGreen = (short) getMinValue(green);
+            mMaxGreen = (short) getMaxValue(green);
+        }
+
+        public PixelColor() {
+            this(BLACK);
+        }
+
+        private int getMinValue(short color) {
+            return Math.max(color - 4, 0);
+        }
+
+        private int getMaxValue(short color) {
+            return Math.min(color + 4, 0xFF);
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index f5fc5c1..70e6f29 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -39,6 +39,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -81,6 +82,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.input.InputManagerService;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerService;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.WindowManagerPolicy;
@@ -93,6 +95,7 @@
 import org.mockito.MockSettings;
 import org.mockito.Mockito;
 import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -283,6 +286,16 @@
         // StatusBarManagerInternal
         final StatusBarManagerInternal sbmi = mock(StatusBarManagerInternal.class);
         doReturn(sbmi).when(() -> LocalServices.getService(eq(StatusBarManagerInternal.class)));
+
+        // UserManagerInternal
+        final UserManagerInternal umi = mock(UserManagerInternal.class);
+        doReturn(umi).when(() -> LocalServices.getService(UserManagerInternal.class));
+        Answer<Boolean> isUserVisibleAnswer = invocation -> {
+            int userId = invocation.getArgument(0);
+            return userId == mWmService.mCurrentUserId;
+        };
+        when(umi.isUserVisible(anyInt())).thenAnswer(isUserVisibleAnswer);
+        when(umi.isUserVisible(anyInt(), anyInt())).thenAnswer(isUserVisibleAnswer);
     }
 
     private void setUpActivityTaskManagerService() {
@@ -403,6 +416,7 @@
         LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
     }
 
     Description getDescription() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 9bdf750..61cf8cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
 import static android.window.TaskFragmentOrganizer.getTransitionType;
@@ -76,6 +77,7 @@
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentOrganizerToken;
+import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
@@ -271,7 +273,7 @@
     @Test
     public void testOnTaskFragmentParentInfoChanged() {
         setupMockParent(mTaskFragment, mTask);
-        mTask.getConfiguration().smallestScreenWidthDp = 10;
+        mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 10;
 
         mController.onTaskFragmentAppeared(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
@@ -295,7 +297,7 @@
 
         // Trigger callback if the size is changed.
         clearInvocations(mOrganizer);
-        mTask.getConfiguration().smallestScreenWidthDp = 100;
+        mTask.getTaskFragmentParentInfo().getConfiguration().smallestScreenWidthDp = 100;
         mController.onTaskFragmentInfoChanged(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
@@ -304,7 +306,8 @@
 
         // Trigger callback if the windowing mode is changed.
         clearInvocations(mOrganizer);
-        mTask.getConfiguration().windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
+        mTask.getTaskFragmentParentInfo().getConfiguration().windowConfiguration
+                .setWindowingMode(WINDOWING_MODE_PINNED);
         mController.onTaskFragmentInfoChanged(
                 mTaskFragment.getTaskFragmentOrganizer(), mTaskFragment);
         mController.dispatchPendingEvents();
@@ -1268,7 +1271,7 @@
         final TaskFragmentTransaction.Change change = changes.get(0);
         assertEquals(TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED, change.getType());
         assertEquals(task.mTaskId, change.getTaskId());
-        assertEquals(task.getConfiguration(), change.getTaskConfiguration());
+        assertEquals(task.getTaskFragmentParentInfo(), change.getTaskFragmentParentInfo());
     }
 
     /** Asserts that there will be a transaction for TaskFragment error. */
@@ -1316,8 +1319,8 @@
     /** Setups the mock Task as the parent of the given TaskFragment. */
     private static void setupMockParent(TaskFragment taskFragment, Task mockParent) {
         doReturn(mockParent).when(taskFragment).getTask();
-        final Configuration taskConfig = new Configuration();
-        doReturn(taskConfig).when(mockParent).getConfiguration();
+        doReturn(new TaskFragmentParentInfo(new Configuration(), DEFAULT_DISPLAY, true))
+                .when(mockParent).getTaskFragmentParentInfo();
 
         // Task needs to be visible
         mockParent.lastActiveTime = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e2dff96..3331839 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -34,6 +34,7 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -1077,6 +1078,39 @@
     }
 
     @Test
+    public void testIsBehindStartingWindowChange() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activity0 = createActivityRecord(task);
+        final ActivityRecord activity1 = createActivityRecord(task);
+        doReturn(true).when(activity1).hasStartingWindow();
+
+        // Start states.
+        changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+        changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
+        // End states.
+        activity0.mVisibleRequested = false;
+        activity1.mVisibleRequested = true;
+
+        participants.add(activity0);
+        participants.add(activity1);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // All windows in the Task should have FLAG_IS_BEHIND_STARTING_WINDOW because the starting
+        // window should cover the whole Task.
+        assertEquals(2, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+        assertTrue(info.getChanges().get(1).hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW));
+
+    }
+
+    @Test
     public void testFlagInTaskWithEmbeddedActivity() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
@@ -1123,7 +1157,7 @@
     }
 
     @Test
-    public void testFlagFillsTask() {
+    public void testFlagFillsTask_embeddingNotFillingTask() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
         final ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -1168,6 +1202,67 @@
     }
 
     @Test
+    public void testFlagFillsTask_openActivityFillingTask() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        // Set to multi-windowing mode in order to set bounds.
+        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect taskBounds = new Rect(0, 0, 500, 1000);
+        task.setBounds(taskBounds);
+        final ActivityRecord activity = createActivityRecord(task);
+        // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
+        activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+        activity.mVisibleRequested = false;
+        changes.put(activity, new Transition.ChangeInfo(activity));
+        // End states: reset bounds to fill Task.
+        activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
+        activity.mVisibleRequested = true;
+
+        participants.add(activity);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // Opening activity that is filling Task after transition should have the flag.
+        assertEquals(1, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+    }
+
+    @Test
+    public void testFlagFillsTask_closeActivityFillingTask() {
+        final Transition transition = createTestTransition(TRANSIT_CLOSE);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+        final Task task = createTask(mDisplayContent);
+        // Set to multi-windowing mode in order to set bounds.
+        task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        final Rect taskBounds = new Rect(0, 0, 500, 1000);
+        task.setBounds(taskBounds);
+        final ActivityRecord activity = createActivityRecord(task);
+        // Start states: fills Task without override.
+        activity.mVisibleRequested = true;
+        changes.put(activity, new Transition.ChangeInfo(activity));
+        // End states: set bounds to make sure the start bounds is ignored if it is not visible.
+        activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
+        activity.mVisibleRequested = false;
+
+        participants.add(activity);
+        final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+                participants, changes);
+        final TransitionInfo info = Transition.calculateTransitionInfo(
+                transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+        // Closing activity that is filling Task before transition should have the flag.
+        assertEquals(1, info.getChanges().size());
+        assertTrue(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK));
+    }
+
+    @Test
     public void testIncludeEmbeddedActivityReparent() {
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         final Task task = createTask(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8b63904..46b4b76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -41,7 +41,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -69,7 +68,6 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.window.ClientWindowFrames;
-import android.window.ScreenCapture;
 import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
@@ -425,45 +423,6 @@
                 LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
     }
 
-    @Test
-    public void testCaptureDisplay() {
-        Rect displayBounds = new Rect(0, 0, 100, 200);
-        spyOn(mDisplayContent);
-        when(mDisplayContent.getBounds()).thenReturn(displayBounds);
-
-        // Null captureArgs
-        ScreenCapture.LayerCaptureArgs resultingArgs =
-                mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */);
-        assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
-        // Non null captureArgs, didn't set rect
-        ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build();
-        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
-        assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
-        // Non null captureArgs, invalid rect
-        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
-                .setSourceCrop(new Rect(0, 0, -1, -1))
-                .build();
-        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
-        assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
-        // Non null captureArgs, null rect
-        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
-                .setSourceCrop(null)
-                .build();
-        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
-        assertEquals(displayBounds, resultingArgs.mSourceCrop);
-
-        // Non null captureArgs, valid rect
-        Rect validRect = new Rect(0, 0, 10, 50);
-        captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
-                .setSourceCrop(validRect)
-                .build();
-        resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
-        assertEquals(validRect, resultingArgs.mSourceCrop);
-    }
-
     private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 9562c1a..e90a376 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -514,6 +514,8 @@
         private boolean mConfigured;
         private boolean mAudioAccessoryConnected;
         private boolean mAudioAccessorySupported;
+        private boolean mConnectedToDataDisabledPort;
+        private int mPowerBrickConnectionStatus;
 
         private UsbAccessory mCurrentAccessory;
         private int mUsbNotificationId;
@@ -952,12 +954,19 @@
                                 && status.isRoleCombinationSupported(POWER_ROLE_SOURCE,
                                 DATA_ROLE_DEVICE)
                                 && status.isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+
+                        boolean usbDataDisabled =
+                                status.getUsbDataStatus() != UsbPortStatus.DATA_STATUS_ENABLED;
+                        mConnectedToDataDisabledPort = status.isConnected() && usbDataDisabled;
+                        mPowerBrickConnectionStatus = status.getPowerBrickConnectionStatus();
                     } else {
                         mHostConnected = false;
                         mSourcePower = false;
                         mSinkPower = false;
                         mAudioAccessoryConnected = false;
                         mSupportsAllCombinations = false;
+                        mConnectedToDataDisabledPort = false;
+                        mPowerBrickConnectionStatus = UsbPortStatus.POWER_BRICK_STATUS_UNKNOWN;
                     }
 
                     if (mHostConnected) {
@@ -1265,6 +1274,12 @@
             } else if (mHostConnected && mSinkPower && (mUsbCharging || mUsbAccessoryConnected)) {
                 titleRes = com.android.internal.R.string.usb_charging_notification_title;
                 id = SystemMessage.NOTE_USB_CHARGING;
+            } else if (mSinkPower && mConnectedToDataDisabledPort
+                    && mPowerBrickConnectionStatus != UsbPortStatus.POWER_BRICK_STATUS_CONNECTED) {
+                // Show charging notification when USB Data is disabled on the port, and not
+                // connected to a wall charger.
+                titleRes = com.android.internal.R.string.usb_charging_notification_title;
+                id = SystemMessage.NOTE_USB_CHARGING;
             }
             if (id != mUsbNotificationId || force) {
                 // clear notification if title needs changing
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 58974ee..061b71b 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -105,17 +105,23 @@
      * @param carrierId the carrier of the id associated with this event.
      */
     public static void reportAnomaly(@NonNull UUID eventId, String description, int carrierId) {
-        // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
-        // related config hasn't loaded.
-        boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED, false);
-        if (!isAnomalyReportEnabledFromServer) return;
-
         if (sContext == null) {
             Rlog.w(TAG, "AnomalyReporter not yet initialized, dropping event=" + eventId);
             return;
         }
 
+        // Don't report if the server-side flag isn't loaded, as it implies other anomaly report
+        // related config hasn't loaded.
+        try {
+            boolean isAnomalyReportEnabledFromServer = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_TELEPHONY, KEY_IS_TELEPHONY_ANOMALY_REPORT_ENABLED,
+                    false);
+            if (!isAnomalyReportEnabledFromServer) return;
+        } catch (Exception e) {
+            Rlog.w(TAG, "Unable to read device config, dropping event=" + eventId);
+            return;
+        }
+
         TelephonyStatsLog.write(
                 TELEPHONY_ANOMALY_DETECTED,
                 carrierId,
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index c75de42..ac892da 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -265,7 +265,7 @@
     /** @hide */
     @Override
     public void onDestroy() {
-        mHandlerThread.quit();
+        mHandlerThread.quitSafely();
         super.onDestroy();
     }
 
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index cb985bf..eb96d37 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -18,6 +18,7 @@
 
 import static android.text.TextUtils.formatSimple;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +38,9 @@
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.os.Parcelable;
+import android.telephony.SubscriptionManager.ProfileClass;
+import android.telephony.SubscriptionManager.SimDisplayNameSource;
+import android.telephony.SubscriptionManager.SubscriptionType;
 import android.telephony.SubscriptionManager.UsageSetting;
 import android.text.TextUtils;
 import android.util.DisplayMetrics;
@@ -55,7 +59,6 @@
  * A Parcelable class for Subscription Information.
  */
 public class SubscriptionInfo implements Parcelable {
-
     /**
      * Size of text to render on the icon.
      */
@@ -65,162 +68,180 @@
      * Subscription Identifier, this is a device unique number
      * and not an index into an array
      */
-    private int mId;
+    private final int mId;
 
     /**
-     * The GID for a SIM that maybe associated with this subscription, empty if unknown
+     * The ICCID of the SIM that is associated with this subscription, empty if unknown.
      */
-    private String mIccId;
+    @NonNull
+    private final String mIccId;
 
     /**
-     * The index of the slot that currently contains the subscription
-     * and not necessarily unique and maybe INVALID_SLOT_ID if unknown
+     * The index of the SIM slot that currently contains the subscription and not necessarily unique
+     * and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the subscription
+     * is inactive.
      */
-    private int mSimSlotIndex;
+    private final int mSimSlotIndex;
 
     /**
-     * The name displayed to the user that identifies this subscription
+     * The name displayed to the user that identifies this subscription. This name is used
+     * in Settings page and can be renamed by the user.
      */
-    private CharSequence mDisplayName;
+    @NonNull
+    private final CharSequence mDisplayName;
 
     /**
-     * String that identifies SPN/PLMN
-     * TODO : Add a new field that identifies only SPN for a sim
+     * The name displayed to the user that identifies subscription provider name. This name is the
+     * SPN displayed in status bar and many other places. Can't be renamed by the user.
      */
-    private CharSequence mCarrierName;
+    @NonNull
+    private final CharSequence mCarrierName;
 
     /**
      * The subscription carrier id.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
-    private int mCarrierId;
+    private final int mCarrierId;
 
     /**
-     * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN,
-     * NAME_SOURCE_SIM_PNN, or NAME_SOURCE_USER_INPUT.
+     * The source of the {@link #mCarrierName}.
      */
-    private int mNameSource;
+    @SimDisplayNameSource
+    private final int mNameSource;
 
     /**
-     * The color to be used for tinting the icon when displaying to the user
+     * The color to be used for tinting the icon when displaying to the user.
      */
-    private int mIconTint;
+    private final int mIconTint;
 
     /**
-     * A number presented to the user identify this subscription
+     * The number presented to the user identify this subscription.
      */
-    private String mNumber;
+    @NonNull
+    private final String mNumber;
 
     /**
-     * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
-    private int mDataRoaming;
+    private final int mDataRoaming;
 
     /**
-     * SIM icon bitmap cache
-     */
-    @Nullable private Bitmap mIconBitmap;
-
-    /**
-     * Mobile Country Code
-     */
-    private String mMcc;
-
-    /**
-     * Mobile Network Code
-     */
-    private String mMnc;
-
-    /**
-     * EHPLMNs associated with the subscription
-     */
-    private String[] mEhplmns;
-
-    /**
-     * HPLMNs associated with the subscription
-     */
-    private String[] mHplmns;
-
-    /**
-     * ISO Country code for the subscription's provider
-     */
-    private String mCountryIso;
-
-    /**
-     * Whether the subscription is an embedded one.
-     */
-    private boolean mIsEmbedded;
-
-    /**
-     * The access rules for this subscription, if it is embedded and defines any.
-     * This does not include access rules for non-embedded subscriptions.
+     * SIM icon bitmap cache.
      */
     @Nullable
-    private UiccAccessRule[] mNativeAccessRules;
+    private Bitmap mIconBitmap;
+
+    /**
+     * Mobile Country Code.
+     */
+    @Nullable
+    private final String mMcc;
+
+    /**
+     * Mobile Network Code.
+     */
+    @Nullable
+    private final String mMnc;
+
+    /**
+     * EHPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mEhplmns;
+
+    /**
+     * HPLMNs associated with the subscription.
+     */
+    @NonNull
+    private final String[] mHplmns;
+
+    /**
+     * ISO Country code for the subscription's provider.
+     */
+    @NonNull
+    private final String mCountryIso;
+
+    /**
+     * Whether the subscription is from eSIM.
+     */
+    private final boolean mIsEmbedded;
+
+    /**
+     * The access rules for this subscription, if it is embedded and defines any. This does not
+     * include access rules for non-embedded subscriptions.
+     */
+    @Nullable
+    private final UiccAccessRule[] mNativeAccessRules;
 
     /**
      * The carrier certificates for this subscription that are saved in carrier configs.
      * This does not include access rules from the Uicc, whether embedded or non-embedded.
      */
     @Nullable
-    private UiccAccessRule[] mCarrierConfigAccessRules;
+    private final UiccAccessRule[] mCarrierConfigAccessRules;
 
     /**
      * The string ID of the SIM card. It is the ICCID of the active profile for a UICC card and the
      * EID for an eUICC card.
      */
-    private String mCardString;
+    @NonNull
+    private final String mCardString;
 
     /**
-     * The card ID of the SIM card. This maps uniquely to the card string.
+     * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
      */
-    private int mCardId;
+    private final int mCardId;
 
     /**
      * Whether the subscription is opportunistic.
      */
-    private boolean mIsOpportunistic;
+    private final boolean mIsOpportunistic;
 
     /**
-     * A UUID assigned to the subscription group. It returns null if not assigned.
-     * Check {@link SubscriptionManager#createSubscriptionGroup(List)} for more details.
+     * A UUID assigned to the subscription group. {@code null} if not assigned.
+     *
+     * @see SubscriptionManager#createSubscriptionGroup(List)
      */
     @Nullable
-    private ParcelUuid mGroupUUID;
+    private final ParcelUuid mGroupUuid;
 
     /**
-     * A package name that specifies who created the group. Null if mGroupUUID is null.
+     * A package name that specifies who created the group. Empty if not available.
      */
-    private String mGroupOwner;
+    @NonNull
+    private final String mGroupOwner;
 
     /**
-     * Whether group of the subscription is disabled.
-     * This is only useful if it's a grouped opportunistic subscription. In this case, if all
-     * primary (non-opportunistic) subscriptions in the group are deactivated (unplugged pSIM
-     * or deactivated eSIM profile), we should disable this opportunistic subscription.
+     * Whether group of the subscription is disabled. This is only useful if it's a grouped
+     * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions
+     * in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we should disable
+     * this opportunistic subscription.
      */
-    private boolean mIsGroupDisabled = false;
+    private final boolean mIsGroupDisabled;
 
     /**
-     * Profile class, PROFILE_CLASS_TESTING, PROFILE_CLASS_OPERATIONAL
-     * PROFILE_CLASS_PROVISIONING, or PROFILE_CLASS_UNSET.
-     * A profile on the eUICC can be defined as test, operational, provisioning, or unset.
-     * The profile class will be populated from the profile metadata if present. Otherwise,
-     * the profile class defaults to unset if there is no profile metadata or the subscription
-     * is not on an eUICC ({@link #isEmbedded} returns false).
+     * The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+     * {@code false}).
      */
-    private int mProfileClass;
+    @ProfileClass
+    private final int mProfileClass;
 
     /**
-     * Type of subscription
+     * Type of the subscription.
      */
-    private int mSubscriptionType;
+    @SubscriptionType
+    private final int mType;
 
     /**
      * Whether uicc applications are configured to enable or disable.
      * By default it's true.
      */
-    private boolean mAreUiccApplicationsEnabled = true;
+    private final boolean mAreUiccApplicationsEnabled;
 
     /**
      * The port index of the Uicc card.
@@ -230,25 +251,16 @@
     /**
      * Subscription's preferred usage setting.
      */
-    private @UsageSetting int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
-
-    /**
-     * Public copy constructor.
-     * @hide
-     */
-    public SubscriptionInfo(SubscriptionInfo info) {
-        this(info.mId, info.mIccId, info.mSimSlotIndex, info.mDisplayName, info.mCarrierName,
-                info.mNameSource, info.mIconTint, info.mNumber, info.mDataRoaming, info.mIconBitmap,
-                info.mMcc, info.mMnc, info.mCountryIso, info.mIsEmbedded, info.mNativeAccessRules,
-                info.mCardString, info.mCardId, info.mIsOpportunistic,
-                info.mGroupUUID == null ? null : info.mGroupUUID.toString(), info.mIsGroupDisabled,
-                info.mCarrierId, info.mProfileClass, info.mSubscriptionType, info.mGroupOwner,
-                info.mCarrierConfigAccessRules, info.mAreUiccApplicationsEnabled);
-    }
+    @UsageSetting
+    private final int mUsageSetting;
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -262,7 +274,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -276,7 +292,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -293,7 +313,11 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@@ -311,49 +335,94 @@
 
     /**
      * @hide
+     *
+     * @deprecated Use {@link SubscriptionInfo.Builder}.
      */
+    // TODO: Clean up after external usages moved to builder model.
+    @Deprecated
     public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
             CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
             Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
             @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId,
-            boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled,
+            boolean isOpportunistic, @Nullable String groupUuid, boolean isGroupDisabled,
             int carrierId, int profileClass, int subType, @Nullable String groupOwner,
             @Nullable UiccAccessRule[] carrierConfigAccessRules,
             boolean areUiccApplicationsEnabled, int portIndex, @UsageSetting int usageSetting) {
         this.mId = id;
         this.mIccId = iccId;
         this.mSimSlotIndex = simSlotIndex;
-        this.mDisplayName = displayName;
+        this.mDisplayName =  displayName;
         this.mCarrierName = carrierName;
         this.mNameSource = nameSource;
         this.mIconTint = iconTint;
         this.mNumber = number;
         this.mDataRoaming = roaming;
         this.mIconBitmap = icon;
-        this.mMcc = mcc;
-        this.mMnc = mnc;
-        this.mCountryIso = countryIso;
+        this.mMcc = TextUtils.emptyIfNull(mcc);
+        this.mMnc = TextUtils.emptyIfNull(mnc);
+        this.mHplmns = null;
+        this.mEhplmns = null;
+        this.mCountryIso = TextUtils.emptyIfNull(countryIso);
         this.mIsEmbedded = isEmbedded;
         this.mNativeAccessRules = nativeAccessRules;
-        this.mCardString = cardString;
+        this.mCardString = TextUtils.emptyIfNull(cardString);
         this.mCardId = cardId;
         this.mIsOpportunistic = isOpportunistic;
-        this.mGroupUUID = groupUUID == null ? null : ParcelUuid.fromString(groupUUID);
+        this.mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
         this.mIsGroupDisabled = isGroupDisabled;
         this.mCarrierId = carrierId;
         this.mProfileClass = profileClass;
-        this.mSubscriptionType = subType;
-        this.mGroupOwner = groupOwner;
+        this.mType = subType;
+        this.mGroupOwner = TextUtils.emptyIfNull(groupOwner);
         this.mCarrierConfigAccessRules = carrierConfigAccessRules;
         this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled;
         this.mPortIndex = portIndex;
         this.mUsageSetting = usageSetting;
     }
+
     /**
-     * @return the subscription ID.
+     * Constructor from builder.
+     *
+     * @param builder Builder of {@link SubscriptionInfo}.
+     */
+    private SubscriptionInfo(@NonNull Builder builder) {
+        this.mId = builder.mId;
+        this.mIccId = builder.mIccId;
+        this.mSimSlotIndex = builder.mSimSlotIndex;
+        this.mDisplayName = builder.mDisplayName;
+        this.mCarrierName = builder.mCarrierName;
+        this.mNameSource = builder.mNameSource;
+        this.mIconTint = builder.mIconTint;
+        this.mNumber = builder.mNumber;
+        this.mDataRoaming = builder.mDataRoaming;
+        this.mIconBitmap = builder.mIconBitmap;
+        this.mMcc = builder.mMcc;
+        this.mMnc = builder.mMnc;
+        this.mEhplmns = builder.mEhplmns;
+        this.mHplmns = builder.mHplmns;
+        this.mCountryIso = builder.mCountryIso;
+        this.mIsEmbedded = builder.mIsEmbedded;
+        this.mNativeAccessRules = builder.mNativeAccessRules;
+        this.mCardString = builder.mCardString;
+        this.mCardId = builder.mCardId;
+        this.mIsOpportunistic = builder.mIsOpportunistic;
+        this.mGroupUuid = builder.mGroupUuid;
+        this.mIsGroupDisabled = builder.mIsGroupDisabled;
+        this.mCarrierId = builder.mCarrierId;
+        this.mProfileClass = builder.mProfileClass;
+        this.mType = builder.mType;
+        this.mGroupOwner = builder.mGroupOwner;
+        this.mCarrierConfigAccessRules = builder.mCarrierConfigAccessRules;
+        this.mAreUiccApplicationsEnabled = builder.mAreUiccApplicationsEnabled;
+        this.mPortIndex = builder.mPortIndex;
+        this.mUsageSetting = builder.mUsageSetting;
+    }
+
+    /**
+     * @return The subscription ID.
      */
     public int getSubscriptionId() {
-        return this.mId;
+        return mId;
     }
 
     /**
@@ -370,78 +439,56 @@
      * @return the ICC ID, or an empty string if one of these requirements is not met
      */
     public String getIccId() {
-        return this.mIccId;
+        return mIccId;
     }
 
     /**
-     * @hide
-     */
-    public void clearIccId() {
-        this.mIccId = "";
-    }
-
-    /**
-     * @return the slot index of this Subscription's SIM card.
+     * @return The index of the SIM slot that currently contains the subscription and not
+     * necessarily unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or
+     * the subscription is inactive.
      */
     public int getSimSlotIndex() {
-        return this.mSimSlotIndex;
+        return mSimSlotIndex;
     }
 
     /**
-     * @return the carrier id of this Subscription carrier.
+     * @return The carrier id of this subscription carrier.
+     *
      * @see TelephonyManager#getSimCarrierId()
      */
     public int getCarrierId() {
-        return this.mCarrierId;
+        return mCarrierId;
     }
 
     /**
-     * @return the name displayed to the user that identifies this subscription
+     * @return The name displayed to the user that identifies this subscription. This name is
+     * used in Settings page and can be renamed by the user.
+     *
+     * @see #getCarrierName()
      */
     public CharSequence getDisplayName() {
-        return this.mDisplayName;
+        return mDisplayName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setDisplayName(CharSequence name) {
-        this.mDisplayName = name;
-    }
-
-    /**
-     * @return the name displayed to the user that identifies Subscription provider name
+     * @return The name displayed to the user that identifies subscription provider name. This name
+     * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+     *
+     * @see #getDisplayName()
      */
     public CharSequence getCarrierName() {
-        return this.mCarrierName;
+        return mCarrierName;
     }
 
     /**
-     * Sets the name displayed to the user that identifies Subscription provider name
-     * @hide
-     */
-    public void setCarrierName(CharSequence name) {
-        this.mCarrierName = name;
-    }
-
-    /**
-     * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SPN or
-     * NAME_SOURCE_USER_INPUT.
+     * @return The source of the {@link #getCarrierName()}.
+     *
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SimDisplayNameSource
     public int getNameSource() {
-        return this.mNameSource;
-    }
-
-    /**
-     * @hide
-     */
-    public void setAssociatedPlmns(String[] ehplmns, String[] hplmns) {
-        mEhplmns = ehplmns;
-        mHplmns = hplmns;
+        return mNameSource;
     }
 
     /**
@@ -499,15 +546,6 @@
     }
 
     /**
-     * Sets the color displayed to the user that identifies this subscription
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void setIconTint(int iconTint) {
-        this.mIconTint = iconTint;
-    }
-
-    /**
      * Returns the number of this subscription.
      *
      * Starting with API level 30, returns the number of this subscription if the calling app meets
@@ -533,28 +571,23 @@
     }
 
     /**
-     * @hide
-     */
-    public void clearNumber() {
-        mNumber = "";
-    }
-
-    /**
-     * @return the data roaming state for this subscription, either
-     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+     * Whether user enables data roaming for this subscription or not. Either
+     * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+     * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
      */
     public int getDataRoaming() {
-        return this.mDataRoaming;
+        return mDataRoaming;
     }
 
     /**
-     * @return the MCC.
+     * @return The mobile country code.
+     *
      * @deprecated Use {@link #getMccString()} instead.
      */
     @Deprecated
     public int getMcc() {
         try {
-            return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc);
+            return mMcc == null ? 0 : Integer.parseInt(mMcc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number");
             return 0;
@@ -562,13 +595,14 @@
     }
 
     /**
-     * @return the MNC.
+     * @return The mobile network code.
+     *
      * @deprecated Use {@link #getMncString()} instead.
      */
     @Deprecated
     public int getMnc() {
         try {
-            return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc);
+            return mMnc == null ? 0 : Integer.parseInt(mMnc);
         } catch (NumberFormatException e) {
             Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number");
             return 0;
@@ -576,36 +610,40 @@
     }
 
     /**
-     * @return The MCC, as a string.
+     * @return The mobile country code.
      */
-    public @Nullable String getMccString() {
-        return this.mMcc;
+    @Nullable
+    public String getMccString() {
+        return mMcc;
     }
 
     /**
-     * @return The MNC, as a string.
+     * @return The mobile network code.
      */
-    public @Nullable String getMncString() {
-        return this.mMnc;
+    @Nullable
+    public String getMncString() {
+        return mMnc;
     }
 
     /**
-     * @return the ISO country code
+     * @return The ISO country code. Empty if not available.
      */
     public String getCountryIso() {
-        return this.mCountryIso;
+        return mCountryIso;
     }
 
-    /** @return whether the subscription is an eUICC one. */
+    /**
+     * @return {@code true} if the subscription is from eSIM.
+     */
     public boolean isEmbedded() {
-        return this.mIsEmbedded;
+        return mIsEmbedded;
     }
 
     /**
      * An opportunistic subscription connects to a network that is
      * limited in functionality and / or coverage.
      *
-     * @return whether subscription is opportunistic.
+     * @return Whether subscription is opportunistic.
      */
     public boolean isOpportunistic() {
         return mIsOpportunistic;
@@ -617,23 +655,18 @@
      * Such that those subscriptions will have some affiliated behaviors such as opportunistic
      * subscription may be invisible to the user.
      *
-     * @return group UUID a String of group UUID if it belongs to a group. Otherwise
-     * it will return null.
+     * @return Group UUID a String of group UUID if it belongs to a group. Otherwise
+     * {@code null}.
      */
-    public @Nullable ParcelUuid getGroupUuid() {
-        return mGroupUUID;
+    @Nullable
+    public ParcelUuid getGroupUuid() {
+        return mGroupUuid;
     }
 
     /**
      * @hide
      */
-    public void clearGroupUuid() {
-        this.mGroupUUID = null;
-    }
-
-    /**
-     * @hide
-     */
+    @NonNull
     public List<String> getEhplmns() {
         return mEhplmns == null ? Collections.emptyList() : Arrays.asList(mEhplmns);
     }
@@ -641,36 +674,45 @@
     /**
      * @hide
      */
+    @NonNull
     public List<String> getHplmns() {
         return mHplmns == null ? Collections.emptyList() : Arrays.asList(mHplmns);
     }
 
     /**
-     * Return owner package of group the subscription belongs to.
+     * @return The owner package of group the subscription belongs to.
      *
      * @hide
      */
-    public @Nullable String getGroupOwner() {
+    @NonNull
+    public String getGroupOwner() {
         return mGroupOwner;
     }
 
     /**
-     * @return the profile class of this subscription.
+     * @return The profile class populated from the profile metadata if present. Otherwise,
+     * the profile class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no
+     * profile metadata or the subscription is not on an eUICC ({@link #isEmbedded} return
+     * {@code false}).
+     *
      * @hide
      */
     @SystemApi
-    public @SubscriptionManager.ProfileClass int getProfileClass() {
-        return this.mProfileClass;
+    @ProfileClass
+    public int getProfileClass() {
+        return mProfileClass;
     }
 
     /**
      * This method returns the type of a subscription. It can be
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
      * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
-     * @return the type of subscription
+     *
+     * @return The type of the subscription.
      */
-    public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
-        return mSubscriptionType;
+    @SubscriptionType
+    public int getSubscriptionType() {
+        return mType;
     }
 
     /**
@@ -679,7 +721,7 @@
      * returns true).
      *
      * @param context Context of the application to check.
-     * @return whether the app is authorized to manage this subscription per its metadata.
+     * @return Whether the app is authorized to manage this subscription per its metadata.
      * @hide
      * @deprecated - Do not use.
      */
@@ -700,7 +742,7 @@
      */
     @Deprecated
     public boolean canManageSubscription(Context context, String packageName) {
-        List<UiccAccessRule> allAccessRules = getAllAccessRules();
+        List<UiccAccessRule> allAccessRules = getAccessRules();
         if (allAccessRules == null) {
             return false;
         }
@@ -723,27 +765,17 @@
     }
 
     /**
-     * @return the {@link UiccAccessRule}s that are stored in Uicc, dictating who
-     * is authorized to manage this subscription.
-     * TODO and fix it properly in R / master: either deprecate this and have 3 APIs
-     *  native + carrier + all, or have this return all by default.
+     * @return The {@link UiccAccessRule}s that are stored in Uicc, dictating who is authorized to
+     * manage this subscription.
+     *
      * @hide
      */
     @SystemApi
-    public @Nullable List<UiccAccessRule> getAccessRules() {
-        if (mNativeAccessRules == null) return null;
-        return Arrays.asList(mNativeAccessRules);
-    }
-
-    /**
-     * @return the {@link UiccAccessRule}s that are both stored on Uicc and in carrierConfigs
-     * dictating who is authorized to manage this subscription.
-     * @hide
-     */
-    public @Nullable List<UiccAccessRule> getAllAccessRules() {
+    @Nullable
+    public List<UiccAccessRule> getAccessRules() {
         List<UiccAccessRule> merged = new ArrayList<>();
         if (mNativeAccessRules != null) {
-            merged.addAll(getAccessRules());
+            merged.addAll(Arrays.asList(mNativeAccessRules));
         }
         if (mCarrierConfigAccessRules != null) {
             merged.addAll(Arrays.asList(mCarrierConfigAccessRules));
@@ -762,50 +794,38 @@
      * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile
      * owner access is deprecated and will be removed in a future release.
      *
-     * @return the card string of the SIM card which contains the subscription or an empty string
+     * @return The card string of the SIM card which contains the subscription or an empty string
      * if these requirements are not met. The card string is the ICCID for UICCs or the EID for
      * eUICCs.
+     *
      * @hide
-     * //TODO rename usages in LPA: UiccSlotUtil.java, UiccSlotsManager.java, UiccSlotInfoTest.java
      */
+    @NonNull
     public String getCardString() {
-        return this.mCardString;
+        return mCardString;
     }
 
     /**
-     * @hide
-     */
-    public void clearCardString() {
-        this.mCardString = "";
-    }
-
-    /**
-     * Returns the card ID of the SIM card which contains the subscription (see
-     * {@link UiccCardInfo#getCardId()}.
-     * @return the cardId
+     * @return The card ID of the SIM card which contains the subscription.
+     *
+     * @see UiccCardInfo#getCardId().
      */
     public int getCardId() {
-        return this.mCardId;
+        return mCardId;
     }
     /**
-     * Returns the port index of the SIM card which contains the subscription.
-     *
-     * @return the portIndex
+     * @return The port index of the SIM card which contains the subscription.
      */
     public int getPortIndex() {
-        return this.mPortIndex;
+        return mPortIndex;
     }
 
     /**
-     * Set whether the subscription's group is disabled.
-     * @hide
-     */
-    public void setGroupDisabled(boolean isGroupDisabled) {
-        this.mIsGroupDisabled = isGroupDisabled;
-    }
-
-    /**
-     * Return whether the subscription's group is disabled.
+     * @return {@code true} if the group of the subscription is disabled. This is only useful if
+     * it's a grouped opportunistic subscription. In this case, if all primary (non-opportunistic)
+     * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile), we
+     * should disable this opportunistic subscription.
+     *
      * @hide
      */
     @SystemApi
@@ -814,7 +834,7 @@
     }
 
     /**
-     * Return whether uicc applications are set to be enabled or disabled.
+     * @return {@code true} if Uicc applications are set to be enabled or disabled.
      * @hide
      */
     @SystemApi
@@ -825,56 +845,50 @@
     /**
      * Get the usage setting for this subscription.
      *
-     * @return the usage setting used for this subscription.
+     * @return The usage setting used for this subscription.
      */
-    public @UsageSetting int getUsageSetting() {
+    @UsageSetting
+    public int getUsageSetting() {
         return mUsageSetting;
     }
 
-    public static final @android.annotation.NonNull
-            Parcelable.Creator<SubscriptionInfo> CREATOR =
-                    new Parcelable.Creator<SubscriptionInfo>() {
+    @NonNull
+    public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
+            new Parcelable.Creator<SubscriptionInfo>() {
         @Override
         public SubscriptionInfo createFromParcel(Parcel source) {
-            int id = source.readInt();
-            String iccId = source.readString();
-            int simSlotIndex = source.readInt();
-            CharSequence displayName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            CharSequence carrierName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-            int nameSource = source.readInt();
-            int iconTint = source.readInt();
-            String number = source.readString();
-            int dataRoaming = source.readInt();
-            String mcc = source.readString();
-            String mnc = source.readString();
-            String countryIso = source.readString();
-            boolean isEmbedded = source.readBoolean();
-            UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR);
-            String cardString = source.readString();
-            int cardId = source.readInt();
-            int portId = source.readInt();
-            boolean isOpportunistic = source.readBoolean();
-            String groupUUID = source.readString();
-            boolean isGroupDisabled = source.readBoolean();
-            int carrierid = source.readInt();
-            int profileClass = source.readInt();
-            int subType = source.readInt();
-            String[] ehplmns = source.createStringArray();
-            String[] hplmns = source.createStringArray();
-            String groupOwner = source.readString();
-            UiccAccessRule[] carrierConfigAccessRules = source.createTypedArray(
-                UiccAccessRule.CREATOR);
-            boolean areUiccApplicationsEnabled = source.readBoolean();
-            int usageSetting = source.readInt();
-
-            SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
-                    carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null,
-                    mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId,
-                    isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType,
-                    groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled,
-                    portId, usageSetting);
-            info.setAssociatedPlmns(ehplmns, hplmns);
-            return info;
+            return new Builder()
+                    .setId(source.readInt())
+                    .setIccId(source.readString())
+                    .setSimSlotIndex(source.readInt())
+                    .setDisplayName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setCarrierName(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source))
+                    .setNameSource(source.readInt())
+                    .setIconTint(source.readInt())
+                    .setNumber(source.readString())
+                    .setDataRoaming(source.readInt())
+                    .setMcc(source.readString())
+                    .setMnc(source.readString())
+                    .setCountryIso(source.readString())
+                    .setEmbedded(source.readBoolean())
+                    .setNativeAccessRules(source.createTypedArray(UiccAccessRule.CREATOR))
+                    .setCardString(source.readString())
+                    .setCardId(source.readInt())
+                    .setPortIndex(source.readInt())
+                    .setOpportunistic(source.readBoolean())
+                    .setGroupUuid(source.readString8())
+                    .setGroupDisabled(source.readBoolean())
+                    .setCarrierId(source.readInt())
+                    .setProfileClass(source.readInt())
+                    .setType(source.readInt())
+                    .setEhplmns(source.createStringArray())
+                    .setHplmns(source.createStringArray())
+                    .setGroupOwner(source.readString())
+                    .setCarrierConfigAccessRules(source.createTypedArray(
+                            UiccAccessRule.CREATOR))
+                    .setUiccApplicationsEnabled(source.readBoolean())
+                    .setUsageSetting(source.readInt())
+                    .build();
         }
 
         @Override
@@ -904,11 +918,11 @@
         dest.writeInt(mCardId);
         dest.writeInt(mPortIndex);
         dest.writeBoolean(mIsOpportunistic);
-        dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString());
+        dest.writeString8(mGroupUuid == null ? null : mGroupUuid.toString());
         dest.writeBoolean(mIsGroupDisabled);
         dest.writeInt(mCarrierId);
         dest.writeInt(mProfileClass);
-        dest.writeInt(mSubscriptionType);
+        dest.writeInt(mType);
         dest.writeStringArray(mEhplmns);
         dest.writeStringArray(mHplmns);
         dest.writeString(mGroupOwner);
@@ -923,6 +937,11 @@
     }
 
     /**
+     * Get ICCID stripped PII information on user build.
+     *
+     * @param iccId The original ICCID.
+     * @return The stripped string.
+     *
      * @hide
      */
     public static String givePrintableIccid(String iccId) {
@@ -951,12 +970,12 @@
                 + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules)
                 + " cardString=" + cardStringToPrint + " cardId=" + mCardId
                 + " portIndex=" + mPortIndex
-                + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID
+                + " isOpportunistic=" + mIsOpportunistic + " groupUuid=" + mGroupUuid
                 + " isGroupDisabled=" + mIsGroupDisabled
                 + " profileClass=" + mProfileClass
                 + " ehplmns=" + Arrays.toString(mEhplmns)
                 + " hplmns=" + Arrays.toString(mHplmns)
-                + " subscriptionType=" + mSubscriptionType
+                + " mType=" + mType
                 + " groupOwner=" + mGroupOwner
                 + " carrierConfigAccessRules=" + Arrays.toString(mCarrierConfigAccessRules)
                 + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
@@ -966,7 +985,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
-                mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
+                mIsOpportunistic, mGroupUuid, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString,
                 mCardId, mDisplayName, mCarrierName, Arrays.hashCode(mNativeAccessRules),
                 mIsGroupDisabled, mCarrierId, mProfileClass, mGroupOwner,
                 mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting);
@@ -974,16 +993,9 @@
 
     @Override
     public boolean equals(Object obj) {
-        if (obj == null) return false;
-        if (obj == this) return true;
-
-        SubscriptionInfo toCompare;
-        try {
-            toCompare = (SubscriptionInfo) obj;
-        } catch (ClassCastException ex) {
-            return false;
-        }
-
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+        SubscriptionInfo toCompare = (SubscriptionInfo) obj;
         return mId == toCompare.mId
                 && mSimSlotIndex == toCompare.mSimSlotIndex
                 && mNameSource == toCompare.mNameSource
@@ -994,7 +1006,7 @@
                 && mIsGroupDisabled == toCompare.mIsGroupDisabled
                 && mAreUiccApplicationsEnabled == toCompare.mAreUiccApplicationsEnabled
                 && mCarrierId == toCompare.mCarrierId
-                && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
+                && Objects.equals(mGroupUuid, toCompare.mGroupUuid)
                 && Objects.equals(mIccId, toCompare.mIccId)
                 && Objects.equals(mNumber, toCompare.mNumber)
                 && Objects.equals(mMcc, toCompare.mMcc)
@@ -1012,4 +1024,629 @@
                 && Arrays.equals(mHplmns, toCompare.mHplmns)
                 && mUsageSetting == toCompare.mUsageSetting;
     }
+
+    /**
+     * The builder class of {@link SubscriptionInfo}.
+     *
+     * @hide
+     */
+    public static class Builder {
+        /**
+         * The subscription id.
+         */
+        private int mId = 0;
+
+        /**
+         * The ICCID of the SIM that is associated with this subscription, empty if unknown.
+         */
+        @NonNull
+        private String mIccId = "";
+
+        /**
+         * The index of the SIM slot that currently contains the subscription and not necessarily
+         * unique and maybe {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if unknown or the
+         * subscription is inactive.
+         */
+        private int mSimSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mDisplayName = "";
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         */
+        @NonNull
+        private CharSequence mCarrierName = "";
+
+        /**
+         * The source of the carrier name.
+         */
+        @SimDisplayNameSource
+        private int mNameSource = SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+
+        /**
+         * The color to be used for tinting the icon when displaying to the user.
+         */
+        private int mIconTint = 0;
+
+        /**
+         * The number presented to the user identify this subscription.
+         */
+        @NonNull
+        private String mNumber = "";
+
+        /**
+         * Whether user enables data roaming for this subscription or not. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}.
+         */
+        private int mDataRoaming = SubscriptionManager.DATA_ROAMING_DISABLE;
+
+        /**
+         * SIM icon bitmap cache.
+         */
+        @Nullable
+        private Bitmap mIconBitmap = null;
+
+        /**
+         * The mobile country code.
+         */
+        @Nullable
+        private String mMcc = null;
+
+        /**
+         * The mobile network code.
+         */
+        @Nullable
+        private String mMnc = null;
+
+        /**
+         * EHPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mEhplmns = new String[0];
+
+        /**
+         * HPLMNs associated with the subscription.
+         */
+        @NonNull
+        private String[] mHplmns = new String[0];
+
+        /**
+         * The ISO Country code for the subscription's provider.
+         */
+        @NonNull
+        private String mCountryIso = "";
+
+        /**
+         * Whether the subscription is from eSIM.
+         */
+        private boolean mIsEmbedded = false;
+
+        /**
+         * The native access rules for this subscription, if it is embedded and defines any. This
+         * does not include access rules for non-embedded subscriptions.
+         */
+        @Nullable
+        private UiccAccessRule[] mNativeAccessRules = null;
+
+        /**
+         * The card string of the SIM card.
+         */
+        @NonNull
+        private String mCardString = "";
+
+        /**
+         * The card ID of the SIM card which contains the subscription.
+         */
+        private int mCardId = -1;
+
+        /**
+         * Whether the subscription is opportunistic or not.
+         */
+        private boolean mIsOpportunistic = false;
+
+        /**
+         * The group UUID of the subscription group.
+         */
+        @Nullable
+        private ParcelUuid mGroupUuid = null;
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         */
+        private boolean mIsGroupDisabled = false;
+
+        /**
+         * The carrier id.
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+
+        /**
+         * The profile class populated from the profile metadata if present. Otherwise, the profile
+         * class defaults to {@link SubscriptionManager#PROFILE_CLASS_UNSET} if there is no profile
+         * metadata or the subscription is not on an eUICC ({@link #isEmbedded} returns
+         * {@code false}).
+         */
+        @ProfileClass
+        private int mProfileClass = SubscriptionManager.PROFILE_CLASS_UNSET;
+
+        /**
+         * The subscription type.
+         */
+        @SubscriptionType
+        private int mType = SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM;
+
+        /**
+         * The owner package of group the subscription belongs to.
+         */
+        @NonNull
+        private String mGroupOwner = "";
+
+        /**
+         * The carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         */
+        @Nullable
+        private UiccAccessRule[] mCarrierConfigAccessRules = null;
+
+        /**
+         * Whether Uicc applications are configured to enable or not.
+         */
+        private boolean mAreUiccApplicationsEnabled = true;
+
+        /**
+         * the port index of the Uicc card.
+         */
+        private int mPortIndex = 0;
+
+        /**
+         * Subscription's preferred usage setting.
+         */
+        @UsageSetting
+        private int mUsageSetting = SubscriptionManager.USAGE_SETTING_UNKNOWN;
+
+        /**
+         * Default constructor.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Constructor from {@link SubscriptionInfo}.
+         *
+         * @param info The subscription info.
+         */
+        public Builder(@NonNull SubscriptionInfo info) {
+            mId = info.mId;
+            mIccId = info.mIccId;
+            mSimSlotIndex = info.mSimSlotIndex;
+            mDisplayName = info.mDisplayName;
+            mCarrierName = info.mCarrierName;
+            mNameSource = info.mNameSource;
+            mIconTint = info.mIconTint;
+            mNumber = info.mNumber;
+            mDataRoaming = info.mDataRoaming;
+            mIconBitmap = info.mIconBitmap;
+            mMcc = info.mMcc;
+            mMnc = info.mMnc;
+            mEhplmns = info.mEhplmns;
+            mHplmns = info.mHplmns;
+            mCountryIso = info.mCountryIso;
+            mIsEmbedded = info.mIsEmbedded;
+            mNativeAccessRules = info.mNativeAccessRules;
+            mCardString = info.mCardString;
+            mCardId = info.mCardId;
+            mIsOpportunistic = info.mIsOpportunistic;
+            mGroupUuid = info.mGroupUuid;
+            mIsGroupDisabled = info.mIsGroupDisabled;
+            mCarrierId = info.mCarrierId;
+            mProfileClass = info.mProfileClass;
+            mType = info.mType;
+            mGroupOwner = info.mGroupOwner;
+            mCarrierConfigAccessRules = info.mCarrierConfigAccessRules;
+            mAreUiccApplicationsEnabled = info.mAreUiccApplicationsEnabled;
+            mPortIndex = info.mPortIndex;
+            mUsageSetting = info.mUsageSetting;
+        }
+
+        /**
+         * Set the subscription id.
+         *
+         * @param id The subscription id.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setId(int id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Set the ICCID of the SIM that is associated with this subscription.
+         *
+         * @param iccId The ICCID of the SIM that is associated with this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIccId(@Nullable String iccId) {
+            mIccId = TextUtils.emptyIfNull(iccId);
+            return this;
+        }
+
+        /**
+         * Set the SIM index of the slot that currently contains the subscription. Set to
+         * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} if the subscription is inactive.
+         *
+         * @param simSlotIndex The SIM slot index.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setSimSlotIndex(int simSlotIndex) {
+            mSimSlotIndex = simSlotIndex;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies this subscription. This name is used
+         * in Settings page and can be renamed by the user.
+         *
+         * @param displayName The display name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDisplayName(@Nullable CharSequence displayName) {
+            mDisplayName = displayName == null ? "" : displayName;
+            return this;
+        }
+
+        /**
+         * The name displayed to the user that identifies subscription provider name. This name
+         * is the SPN displayed in status bar and many other places. Can't be renamed by the user.
+         *
+         * @param carrierName The carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierName(@Nullable CharSequence carrierName) {
+            mCarrierName = carrierName == null ? "" : carrierName;
+            return this;
+        }
+
+        /**
+         * Set the source of the carrier name.
+         *
+         * @param nameSource The source of the carrier name.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNameSource(@SimDisplayNameSource int nameSource) {
+            mNameSource = nameSource;
+            return this;
+        }
+
+        /**
+         * Set the color to be used for tinting the icon when displaying to the user.
+         *
+         * @param iconTint The color to be used for tinting the icon when displaying to the user.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIconTint(int iconTint) {
+            mIconTint = iconTint;
+            return this;
+        }
+
+        /**
+         * Set the number presented to the user identify this subscription.
+         *
+         * @param number the number presented to the user identify this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNumber(@Nullable String number) {
+            mNumber = TextUtils.emptyIfNull(number);
+            return this;
+        }
+
+        /**
+         * Set whether user enables data roaming for this subscription or not.
+         *
+         * @param dataRoaming Data roaming mode. Either
+         * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or
+         * {@link SubscriptionManager#DATA_ROAMING_DISABLE}
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setDataRoaming(int dataRoaming) {
+            mDataRoaming = dataRoaming;
+            return this;
+        }
+
+        /**
+         * Set SIM icon bitmap cache.
+         *
+         * @param iconBitmap SIM icon bitmap cache.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIcon(@Nullable Bitmap iconBitmap) {
+            mIconBitmap = iconBitmap;
+            return this;
+        }
+
+        /**
+         * Set the mobile country code.
+         *
+         * @param mcc The mobile country code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMcc(@Nullable String mcc) {
+            mMcc = mcc;
+            return this;
+        }
+
+        /**
+         * Set the mobile network code.
+         *
+         * @param mnc Mobile network code.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setMnc(@Nullable String mnc) {
+            mMnc = mnc;
+            return this;
+        }
+
+        /**
+         * Set EHPLMNs associated with the subscription.
+         *
+         * @param ehplmns EHPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEhplmns(@Nullable String[] ehplmns) {
+            mEhplmns = ehplmns == null ? new String[0] : ehplmns;
+            return this;
+        }
+
+        /**
+         * Set HPLMNs associated with the subscription.
+         *
+         * @param hplmns HPLMNs associated with the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setHplmns(@Nullable String[] hplmns) {
+            mHplmns = hplmns == null ? new String[0] : hplmns;
+            return this;
+        }
+
+        /**
+         * Set the ISO Country code for the subscription's provider.
+         *
+         * @param countryIso The ISO Country code for the subscription's provider.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCountryIso(@Nullable String countryIso) {
+            mCountryIso = TextUtils.emptyIfNull(countryIso);
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is from eSIM or not.
+         *
+         * @param isEmbedded {@code true} if the subscription is from eSIM.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setEmbedded(boolean isEmbedded) {
+            mIsEmbedded = isEmbedded;
+            return this;
+        }
+
+        /**
+         * Set the native access rules for this subscription, if it is embedded and defines any.
+         * This does not include access rules for non-embedded subscriptions.
+         *
+         * @param nativeAccessRules The native access rules for this subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setNativeAccessRules(@Nullable UiccAccessRule[] nativeAccessRules) {
+            mNativeAccessRules = nativeAccessRules;
+            return this;
+        }
+
+        /**
+         * Set the card string of the SIM card.
+         *
+         * @param cardString The card string of the SIM card.
+         * @return The builder.
+         *
+         * @see #getCardString()
+         */
+        @NonNull
+        public Builder setCardString(@Nullable String cardString) {
+            mCardString = TextUtils.emptyIfNull(cardString);
+            return this;
+        }
+
+        /**
+         * Set the card ID of the SIM card which contains the subscription.
+         *
+         * @param cardId The card ID of the SIM card which contains the subscription.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCardId(int cardId) {
+            mCardId = cardId;
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is opportunistic or not.
+         *
+         * @param isOpportunistic {@code true} if the subscription is opportunistic.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setOpportunistic(boolean isOpportunistic) {
+            mIsOpportunistic = isOpportunistic;
+            return this;
+        }
+
+        /**
+         * Set the group UUID of the subscription group.
+         *
+         * @param groupUuid The group UUID.
+         * @return The builder.
+         *
+         * @see #getGroupUuid()
+         */
+        @NonNull
+        public Builder setGroupUuid(@Nullable String groupUuid) {
+            mGroupUuid = groupUuid == null ? null : ParcelUuid.fromString(groupUuid);
+            return this;
+        }
+
+        /**
+         * Whether group of the subscription is disabled. This is only useful if it's a grouped
+         * opportunistic subscription. In this case, if all primary (non-opportunistic)
+         * subscriptions in the group are deactivated (unplugged pSIM or deactivated eSIM profile),
+         * we should disable this opportunistic subscription.
+         *
+         * @param isGroupDisabled {@code true} if group of the subscription is disabled.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupDisabled(boolean isGroupDisabled) {
+            mIsGroupDisabled = isGroupDisabled;
+            return this;
+        }
+
+        /**
+         * Set the subscription carrier id.
+         *
+         * @param carrierId The carrier id.
+         * @return The builder
+         *
+         * @see TelephonyManager#getSimCarrierId()
+         */
+        @NonNull
+        public Builder setCarrierId(int carrierId) {
+            mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
+         * Set the profile class populated from the profile metadata if present.
+         *
+         * @param profileClass the profile class populated from the profile metadata if present.
+         * @return The builder
+         *
+         * @see #getProfileClass()
+         */
+        @NonNull
+        public Builder setProfileClass(@ProfileClass int profileClass) {
+            mProfileClass = profileClass;
+            return this;
+        }
+
+        /**
+         * Set the subscription type.
+         *
+         * @param type Subscription type.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setType(@SubscriptionType int type) {
+            mType = type;
+            return this;
+        }
+
+        /**
+         * Set the owner package of group the subscription belongs to.
+         *
+         * @param groupOwner Owner package of group the subscription belongs to.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setGroupOwner(@Nullable String groupOwner) {
+            mGroupOwner = TextUtils.emptyIfNull(groupOwner);
+            return this;
+        }
+
+        /**
+         * Set the carrier certificates for this subscription that are saved in carrier configs.
+         * This does not include access rules from the Uicc, whether embedded or non-embedded.
+         *
+         * @param carrierConfigAccessRules The carrier certificates for this subscription
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setCarrierConfigAccessRules(
+                @Nullable UiccAccessRule[] carrierConfigAccessRules) {
+            mCarrierConfigAccessRules = carrierConfigAccessRules;
+            return this;
+        }
+
+        /**
+         * Set whether Uicc applications are configured to enable or not.
+         *
+         * @param uiccApplicationsEnabled {@code true} if Uicc applications are configured to
+         * enable.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUiccApplicationsEnabled(boolean uiccApplicationsEnabled) {
+            mAreUiccApplicationsEnabled = uiccApplicationsEnabled;
+            return this;
+        }
+
+        /**
+         * Set the port index of the Uicc card.
+         *
+         * @param portIndex The port index of the Uicc card.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setPortIndex(int portIndex) {
+            mPortIndex = portIndex;
+            return this;
+        }
+
+        /**
+         * Set subscription's preferred usage setting.
+         *
+         * @param usageSetting Subscription's preferred usage setting.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setUsageSetting(@UsageSetting int usageSetting) {
+            mUsageSetting = usageSetting;
+            return this;
+        }
+
+        /**
+         * Build the {@link SubscriptionInfo}.
+         *
+         * @return The {@link SubscriptionInfo} instance.
+         */
+        public SubscriptionInfo build() {
+            return new SubscriptionInfo(this);
+        }
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6bc14bf..87e651b 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1071,6 +1071,13 @@
     public static final String ALLOWED_NETWORK_TYPES =
             SimInfo.COLUMN_ALLOWED_NETWORK_TYPES_FOR_REASONS;
 
+    /**
+     * TelephonyProvider column name for user handle associated with a sim.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String USER_HANDLE = SimInfo.COLUMN_USER_HANDLE;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -3099,7 +3106,7 @@
     @SystemApi
     public boolean canManageSubscription(@NonNull SubscriptionInfo info,
             @NonNull String packageName) {
-        if (info == null || info.getAllAccessRules() == null || packageName == null) {
+        if (info == null || info.getAccessRules() == null || packageName == null) {
             return false;
         }
         PackageManager packageManager = mContext.getPackageManager();
@@ -3111,7 +3118,7 @@
             logd("Unknown package: " + packageName);
             return false;
         }
-        for (UiccAccessRule rule : info.getAllAccessRules()) {
+        for (UiccAccessRule rule : info.getAccessRules()) {
             if (rule.getCarrierPrivilegeStatus(packageInfo)
                     == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                 return true;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 12b4114..b4244dd 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -191,9 +191,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long CALLBACK_ON_MORE_ERROR_CODE_CHANGE = 130595455L;
 
-    // Null IMEI anomaly uuid
-    private static final UUID IMEI_ANOMALY_UUID = UUID.fromString(
-            "83905f14-6455-450c-be29-8206f0427fe9");
     /**
      * The key to use when placing the result of {@link #requestModemActivityInfo(ResultReceiver)}
      * into the ResultReceiver Bundle.
@@ -2184,11 +2181,7 @@
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei() {
-        String imei = getImei(getSlotIndex());
-        if (imei == null) {
-            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: IMEI is null.");
-        }
-        return imei;
+        return getImei(getSlotIndex());
     }
 
     /**
@@ -2231,10 +2224,7 @@
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     public String getImei(int slotIndex) {
         ITelephony telephony = getITelephony();
-        if (telephony == null) {
-            AnomalyReporter.reportAnomaly(IMEI_ANOMALY_UUID, "getImei: telephony is null");
-            return null;
-        }
+        if (telephony == null) return null;
 
         try {
             return telephony.getImeiForSlot(slotIndex, getOpPackageName(), getAttributionTag());
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 700d615..d8b2cbe 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -725,7 +725,7 @@
 
     @Override
     public void onDestroy() {
-        mHandlerThread.quit();
+        mHandlerThread.quitSafely();
         super.onDestroy();
     }
 
diff --git a/tests/FlickerTests/AndroidManifest.xml b/tests/FlickerTests/AndroidManifest.xml
index e173eba0..487a0c3 100644
--- a/tests/FlickerTests/AndroidManifest.xml
+++ b/tests/FlickerTests/AndroidManifest.xml
@@ -40,6 +40,8 @@
     <uses-permission android:name="android.permission.READ_LOGS"/>
     <!-- ATM.removeRootTasksWithActivityTypes() -->
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
+    <!-- ActivityOptions.makeCustomTaskAnimation() -->
+    <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <!-- Allow the test to write directly to /sdcard/ -->
     <application android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
diff --git a/services/tests/wmtests/res/values/styles.xml b/tests/FlickerTests/res/anim/show_2000ms.xml
similarity index 62%
rename from services/tests/wmtests/res/values/styles.xml
rename to tests/FlickerTests/res/anim/show_2000ms.xml
index 6857ff99..76e375f 100644
--- a/services/tests/wmtests/res/values/styles.xml
+++ b/tests/FlickerTests/res/anim/show_2000ms.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
@@ -14,11 +15,7 @@
   ~ limitations under the License.
   -->
 
-<resources>
-    <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:windowFullscreen">true</item>
-        <item name="android:windowIsTranslucent">true</item>
-        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
-    </style>
-</resources>
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="2000"
+    android:fromXDelta="0"
+    android:toXDelta="0" />
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 1e798f3..f77ec8a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -21,13 +21,14 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
 
 /**
- * Base test class containing common assertions for [ComponentMatcher.NAV_BAR],
- * [ComponentMatcher.TASK_BAR], [ComponentMatcher.STATUS_BAR], and general assertions
+ * Base test class containing common assertions for [ComponentNameMatcher.NAV_BAR],
+ * [ComponentNameMatcher.TASK_BAR], [ComponentNameMatcher.STATUS_BAR], and general assertions
  * (layers visible in consecutive states, entire screen covered, etc.)
  */
 abstract class BaseTest @JvmOverloads constructor(
@@ -72,7 +73,7 @@
     open fun entireScreenCovered() = testSpec.entireScreenCovered()
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] layer is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
      *
      * Note: Phones only
      */
@@ -84,7 +85,8 @@
     }
 
     /**
-     * Checks the position of the [ComponentMatcher.NAV_BAR] at the start and end of the transition
+     * Checks the position of the [ComponentNameMatcher.NAV_BAR] at the start and end of the
+     * transition
      *
      * Note: Phones only
      */
@@ -96,7 +98,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.NAV_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.NAV_BAR] window is visible during the whole transition
      *
      * Note: Phones only
      */
@@ -108,8 +110,8 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] window is visible at the start and end of the
-     * transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of
+     * the transition
      *
      * Note: Large screen only
      */
@@ -121,7 +123,8 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.TASK_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole
+     * transition
      *
      * Note: Large screen only
      */
@@ -133,7 +136,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the start and end
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end
      * of the transition
      */
     @Presubmit
@@ -142,7 +145,7 @@
         testSpec.statusBarLayerIsVisibleAtStartAndEnd()
 
     /**
-     * Checks the position of the [ComponentMatcher.STATUS_BAR] at the start and end of the
+     * Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
      * transition
      */
     @Presubmit
@@ -150,7 +153,8 @@
     open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] window is visible during the whole transition
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
+     * transition
      */
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index b8fe9f9..ef5cec2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -33,12 +33,12 @@
 import org.junit.Assume.assumeNotNull
 
 class ActivityEmbeddingAppHelper @JvmOverloads constructor(
-        instr: Instrumentation,
-        launcherName: String = ActivityOptions.ACTIVITY_EMBEDDING_LAUNCHER_NAME,
-        component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
-        launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-                .getInstance(instr)
-                .launcherStrategy
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
+    component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
+    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+        .getInstance(instr)
+        .launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     /**
@@ -47,8 +47,8 @@
      */
     fun launchPlaceholderSplit(wmHelper: WindowManagerStateHelper) {
         val launchButton = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
-                FIND_TIMEOUT)
+            Until.findObject(By.res(getPackage(), "launch_placeholder_split_button")),
+            FIND_TIMEOUT)
         require(launchButton != null) {
             "Can't find launch placeholder split button on screen."
         }
@@ -62,14 +62,15 @@
     companion object {
         private const val TAG = "ActivityEmbeddingAppHelper"
 
-        val MAIN_ACTIVITY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        val MAIN_ACTIVITY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.MainActivity.COMPONENT.toFlickerComponent()
 
-        val PLACEHOLDER_PRIMARY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        val PLACEHOLDER_PRIMARY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+                .toFlickerComponent()
 
-        val PLACEHOLDER_SECONDARY_COMPONENT = ActivityOptions
-                .ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME
+        val PLACEHOLDER_SECONDARY_COMPONENT =
+            ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT
                 .toFlickerComponent()
 
         @JvmStatic
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
index 826cc2e..4ff4e31 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AppPairsHelper.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -23,4 +23,4 @@
     instrumentation: Instrumentation,
     activityLabel: String,
     component: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, component)
+) : StandardAppHelper(instrumentation, activityLabel, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index b696fc3..132e7b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -24,11 +24,11 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class FixedOrientationAppHelper @JvmOverloads constructor(
-     instr: Instrumentation,
-     launcherName: String = ActivityOptions.PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME,
-     component: ComponentNameMatcher =
-             ActivityOptions.PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-             .getInstance(instr)
-             .launcherStrategy
- ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
+    component: ComponentNameMatcher =
+        ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+        .getInstance(instr)
+        .launcherStrategy
+) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index ea5a5f8..2d81e0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -29,9 +29,8 @@
 
 class GameAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.GAME_ACTIVITY_LAUNCHER_NAME,
-    component: ComponentNameMatcher =
-        ActivityOptions.GAME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+    launcherName: String = ActivityOptions.Game.LABEL,
+    component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy =
         LauncherStrategyFactory.getInstance(instr).launcherStrategy,
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index e01cceb..b5b0da9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -32,9 +32,9 @@
     instr: Instrumentation,
     private val rotation: Int,
     private val imePackageName: String = IME_PACKAGE,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
         // do nothing (the app is focused automatically)
@@ -62,21 +62,22 @@
 
     fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
         val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
-                "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
+            "start_dialog_themed_activity_btn")), FIND_TIMEOUT)
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. Screen turned off)"
+                "was left in an unknown state (e.g. Screen turned off)"
         }
         button.click()
         wmHelper.StateSyncBuilder()
             .withFullScreenApp(
-                ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
+                ActivityOptions.DialogThemedActivity.COMPONENT.toFlickerComponent())
             .waitForAndVerify()
     }
+
     fun dismissDialog(wmHelper: WindowManagerStateHelper) {
         val dialog = uiDevice.wait(
-                Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
+            Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT)
 
         // Pressing back key to dismiss the dialog
         if (dialog != null) {
@@ -86,9 +87,10 @@
                 .waitForAndVerify()
         }
     }
+
     fun getInsetsVisibleFromDialog(type: Int): Boolean {
         val insetsVisibilityTextView = uiDevice.wait(
-                Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
+            Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
         if (insetsVisibilityTextView != null) {
             val visibility = insetsVisibilityTextView.text.toString()
             val matcher = when (type) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index b672b1b..56b6b92 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -28,12 +28,11 @@
 
 open class ImeAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.Default.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     /**
      * Opens the IME and wait for it to be displayed
@@ -73,8 +72,8 @@
 
     open fun finishActivity(wmHelper: WindowManagerStateHelper) {
         val finishButton = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "finish_activity_btn")),
-                FIND_TIMEOUT)
+            Until.findObject(By.res(getPackage(), "finish_activity_btn")),
+            FIND_TIMEOUT)
         requireNotNull(finishButton) {
             "Finish activity button not found, probably IME activity is not on the screen?"
         }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
index df47e9d..513103a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -26,16 +26,16 @@
 
 class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
     component: ComponentNameMatcher =
-            ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.Ime.AutoFocusActivity.COMPONENT.toFlickerComponent()
 ) : ImeAppHelper(instr, launcherName, component) {
     override fun openIME(wmHelper: WindowManagerStateHelper) {
         val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
 
-        require(editText != null) {
+        requireNotNull(editText) {
             "Text field not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         editText.click()
         waitIMEShown(wmHelper)
@@ -43,7 +43,7 @@
 
     fun dismissDialog(wmHelper: WindowManagerStateHelper) {
         val dismissButton = uiDevice.wait(
-                Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
+            Until.findObject(By.text("Dismiss")), FIND_TIMEOUT)
 
         // Pressing back key to dismiss the dialog
         if (dismissButton != null) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index d3945c1..3b8d3c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -25,10 +25,9 @@
 
 class ImeStateInitializeHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
similarity index 70%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
index 4d0fbc4..cb54b57 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/LaunchBubbleHelper.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.testapp.Components
 
-class SimpleAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
+class LaunchBubbleHelper(instrumentation: Instrumentation) : StandardAppHelper(
     instrumentation,
-    Components.SimpleActivity.LABEL,
-    Components.SimpleActivity.COMPONENT.toFlickerComponent()
-)
\ No newline at end of file
+    ActivityOptions.Bubbles.LaunchBubble.LABEL,
+    ActivityOptions.Bubbles.LaunchBubble.COMPONENT.toFlickerComponent()
+)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index 807e672..dde0b3e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -28,13 +28,13 @@
 import com.android.server.wm.traces.parser.toFlickerComponent
 
 class MailAppHelper @JvmOverloads constructor(
-        instr: Instrumentation,
-        launcherName: String = ActivityOptions.MAIL_ACTIVITY_LAUNCHER_NAME,
-        component: ComponentNameMatcher =
-                ActivityOptions.MAIL_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-        launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-                .getInstance(instr)
-                .launcherStrategy
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.Mail.LABEL,
+    component: ComponentNameMatcher =
+        ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
+        .getInstance(instr)
+        .launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     fun openMail(rowIdx: Int) {
@@ -46,7 +46,7 @@
             if (row != null) break
             scrollDown()
         }
-        require(row != null) {""}
+        require(row != null) { "" }
         row.click()
         uiDevice.wait(Until.gone(By.res(getPackage(), MAIL_LIST_RES_ID)), FIND_TIMEOUT)
     }
@@ -57,9 +57,9 @@
     }
 
     fun waitForMailList(): UiObject2 {
-        var sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
+        val sel = By.res(getPackage(), MAIL_LIST_RES_ID).scrollable(true)
         val ret = uiDevice.wait(Until.findObject(sel), FIND_TIMEOUT)
-        require(ret != null) {""}
+        requireNotNull(ret) { "Unable to find $MAIL_LIST_RES_ID object" }
         return ret
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
similarity index 77%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
index 245a82f..9bdfadb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/MultiWindowHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MultiWindowUtils.kt
@@ -14,20 +14,31 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
 import android.content.Context
 import android.provider.Settings
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import java.io.IOException
 
-class MultiWindowHelper(
+class MultiWindowUtils(
     instrumentation: Instrumentation,
     activityLabel: String,
     componentsInfo: ComponentNameMatcher
-) : BaseAppHelper(instrumentation, activityLabel, componentsInfo) {
+) : StandardAppHelper(instrumentation, activityLabel, componentsInfo) {
 
     companion object {
+        fun executeShellCommand(instrumentation: Instrumentation, cmd: String) {
+            try {
+                SystemUtil.runShellCommand(instrumentation, cmd)
+            } catch (e: IOException) {
+                Log.e(MultiWindowUtils::class.simpleName, "executeShellCommand error! $e")
+            }
+        }
+
         fun getDevEnableNonResizableMultiWindow(context: Context): Int =
                 Settings.Global.getInt(context.contentResolver,
                         Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 9fb574c..f3386af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -29,9 +29,9 @@
 
 class NewTasksAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+        ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
         .getInstance(instr)
         .launcherStrategy
@@ -43,7 +43,7 @@
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
         wmHelper.StateSyncBuilder()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index a1dbeea..19ce3ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -25,10 +25,9 @@
 
 class NonResizeableAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index b031a45..97642f1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -28,25 +28,28 @@
 
 class NotificationAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.NOTIFICATION_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.Notification.LABEL,
     component: ComponentNameMatcher =
-            ActivityOptions.NOTIFICATION_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
+        ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
     launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        .getInstance(instr)
+        .launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
     fun postNotification(wmHelper: WindowManagerStateHelper) {
         val button = uiDevice.wait(
-                Until.findObject(By.res(getPackage(), "post_notification")),
-                FIND_TIMEOUT)
+            Until.findObject(By.res(getPackage(), "post_notification")),
+            FIND_TIMEOUT)
 
-        require(button != null) {
+        requireNotNull(button) {
             "Post notification button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
 
         uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
-                ?: error("Flicker Notification not found")
+            ?: error("Flicker Notification not found")
+        wmHelper.StateSyncBuilder()
+            .withAppTransitionIdle()
+            .waitForAndVerify()
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
similarity index 72%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
rename to tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index bdc05e7..4d801c90 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -14,28 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.helpers
+package com.android.server.wm.flicker.helpers
 
 import android.app.Instrumentation
 import android.media.session.MediaController
 import android.media.session.MediaSessionManager
 import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
-import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.Rect
 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
-import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild
-import com.android.wm.shell.flicker.testapp.Components
 
-class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
+open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper(
     instrumentation,
-    Components.PipActivity.LABEL,
-    Components.PipActivity.COMPONENT.toFlickerComponent()
+    ActivityOptions.Pip.LABEL,
+    ActivityOptions.Pip.COMPONENT.toFlickerComponent()
 ) {
     private val mediaSessionManager: MediaSessionManager
         get() = context.getSystemService(MediaSessionManager::class.java)
@@ -46,16 +41,11 @@
             it.packageName == `package`
         }
 
-    fun clickObject(resId: String) {
+    open fun clickObject(resId: String) {
         val selector = By.res(`package`, resId)
         val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
 
-        if (!isTelevision) {
-            obj.click()
-        } else {
-            focusOnObject(selector) || error("Could not focus on `$resId` object")
-            uiDevice.pressDPadCenter()
-        }
+        obj.click()
     }
 
     /**
@@ -85,20 +75,6 @@
     fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
         launchViaIntentAndWaitShown(wmHelper)
 
-    private fun focusOnObject(selector: BySelector): Boolean {
-        // We expect all the focusable UI elements to be arranged in a way so that it is possible
-        // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top"
-        // from "the bottom".
-        repeat(FOCUS_ATTEMPTS) {
-            uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
-                ?: error("The object we try to focus on is gone.")
-
-            uiDevice.pressDPadDown()
-            uiDevice.waitForIdle()
-        }
-        return false
-    }
-
     fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
         clickObject(ENTER_PIP_BUTTON_ID)
 
@@ -140,12 +116,8 @@
         "Use PipAppHelper.closePipWindow(wmHelper) instead",
         ReplaceWith("closePipWindow(wmHelper)")
     )
-    fun closePipWindow() {
-        if (isTelevision) {
-            uiDevice.closeTvPipWindow()
-        } else {
-            closePipWindow(WindowManagerStateHelper(mInstrumentation))
-        }
+    open fun closePipWindow() {
+        closePipWindow(WindowManagerStateHelper(mInstrumentation))
     }
 
     private fun getWindowRect(wmHelper: WindowManagerStateHelper): Rect {
@@ -159,20 +131,16 @@
     /**
      * Taps the pip window and dismisses it by clicking on the X button.
      */
-    fun closePipWindow(wmHelper: WindowManagerStateHelper) {
-        if (isTelevision) {
-            uiDevice.closeTvPipWindow()
-        } else {
-            val windowRect = getWindowRect(wmHelper)
-            uiDevice.click(windowRect.centerX(), windowRect.centerY())
-            // search and interact with the dismiss button
-            val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
-            uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
-            val dismissPipObject = uiDevice.findObject(dismissSelector)
-                ?: error("PIP window dismiss button not found")
-            val dismissButtonBounds = dismissPipObject.visibleBounds
-            uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
-        }
+    open fun closePipWindow(wmHelper: WindowManagerStateHelper) {
+        val windowRect = getWindowRect(wmHelper)
+        uiDevice.click(windowRect.centerX(), windowRect.centerY())
+        // search and interact with the dismiss button
+        val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
+        uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
+        val dismissPipObject = uiDevice.findObject(dismissSelector)
+            ?: error("PIP window dismiss button not found")
+        val dismissButtonBounds = dismissPipObject.visibleBounds
+        uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
 
         // Wait for animation to complete.
         wmHelper.StateSyncBuilder()
@@ -213,7 +181,6 @@
     }
 
     companion object {
-        private const val FOCUS_ATTEMPTS = 20
         private const val ENTER_PIP_BUTTON_ID = "enter_pip"
         private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
         private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index 6d466d7..fc1ff7c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -25,10 +25,9 @@
 
 class SeamlessRotationAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SEAMLESS_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.SEAMLESS_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index 804ab38..0e1df41 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -25,10 +25,9 @@
 
 class ShowWhenLockedAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
     component: ComponentNameMatcher =
-            ActivityOptions.SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
+        ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index 5da273a7..e3461a7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -25,10 +25,9 @@
 
 class SimpleAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.SIMPLE_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.SimpleActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 060e9af..f4ea37f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -29,16 +29,15 @@
 
 class TwoActivitiesAppHelper @JvmOverloads constructor(
     instr: Instrumentation,
-    launcherName: String = ActivityOptions.BUTTON_ACTIVITY_LAUNCHER_NAME,
+    launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
     component: ComponentNameMatcher =
-        ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent(),
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-        .getInstance(instr)
-        .launcherStrategy
+        ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
+    launcherStrategy: ILauncherStrategy =
+        LauncherStrategyFactory.getInstance(instr).launcherStrategy
 ) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
 
     private val secondActivityComponent =
-        ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
 
     fun openSecondActivity(device: UiDevice, wmHelper: WindowManagerStateHelper) {
         val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
@@ -46,7 +45,7 @@
 
         requireNotNull(button) {
             "Button not found, this usually happens when the device " +
-                    "was left in an unknown state (e.g. in split screen)"
+                "was left in an unknown state (e.g. in split screen)"
         }
         button.click()
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 08d7be2..f019acc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -83,19 +83,17 @@
     override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
 
     /**
-     * Checks that the [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] activity is visible at
-     * the start of the transition, that
-     * [ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME] becomes visible during the
-     * transition, and that [ActivityOptions.BUTTON_ACTIVITY_COMPONENT_NAME] is again visible
-     * at the end
+     * Checks that the [ActivityOptions.LaunchNewActivity] activity is visible at
+     * the start of the transition, that [ActivityOptions.SimpleActivity] becomes visible during
+     * the transition, and that [ActivityOptions.LaunchNewActivity] is again visible at the end
      */
     @Presubmit
     @Test
     fun finishSubActivity() {
-        val buttonActivityComponent = ActivityOptions
-            .BUTTON_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
-        val imeAutoFocusActivityComponent = ActivityOptions
-            .SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+        val buttonActivityComponent =
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        val imeAutoFocusActivityComponent =
+            ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
         testSpec.assertWm {
             this.isAppWindowOnTop(buttonActivityComponent)
                 .then()
@@ -106,7 +104,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] window is not on top. The launcher cannot be
+     * Checks that the [ComponentNameMatcher.LAUNCHER] window is not on top. The launcher cannot be
      * asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
      * and both are never simultaneously visible
      */
@@ -119,7 +117,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible during the transition
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible during the transition
      */
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
new file mode 100644
index 0000000..4f21412
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.app.Instrumentation
+import android.os.Bundle
+import android.os.Handler
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.R
+import com.android.server.wm.flicker.annotation.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the [android.app.ActivityOptions.makeCustomTaskAnimation].
+ *
+ * To run this test: `atest FlickerTests:OverrideTaskTransitionTest`
+ *
+ * Actions:
+ *     Launches SimpleActivity with alpha_2000ms animation
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group4
+class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+
+    @FlickerBuilderProvider
+    fun buildFlicker(): FlickerBuilder {
+        return FlickerBuilder(instrumentation).apply {
+            setup {
+                device.wakeUpAndGoToHomeScreen()
+                RemoveAllTasksButHomeRule.removeAllTasksButHome()
+                setRotation(testSpec.startRotation)
+            }
+            transitions {
+                instrumentation.context.startActivity(
+                        testApp.openAppIntent, createCustomTaskAnimation())
+                wmHelper.StateSyncBuilder()
+                        .add(WindowManagerConditionsFactory.isWMStateComplete())
+                        .withAppTransitionIdle()
+                        .withWindowSurfaceAppeared(testApp)
+                        .waitForAndVerify()
+            }
+            teardown {
+                testApp.exit()
+            }
+        }
+    }
+
+    @Presubmit
+    @Test
+    fun testSimpleActivityIsShownDirectly() {
+        testSpec.assertLayers {
+            isVisible(ComponentNameMatcher.LAUNCHER)
+                .isInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+                .isInvisible(testApp)
+                .then()
+                // The custom animation should block the entire launcher from the very beginning
+                .isInvisible(ComponentNameMatcher.LAUNCHER)
+        }
+    }
+
+    private fun createCustomTaskAnimation(): Bundle {
+        return android.app.ActivityOptions.makeCustomTaskAnimation(instrumentation.context,
+                R.anim.show_2000ms, 0, Handler.getMain(), null, null).toBundle()
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance()
+                    .getConfigNonRotationTests()
+        }
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 26f46cd..63b78b6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -28,8 +28,7 @@
 import com.android.server.wm.flicker.dsl.FlickerBuilder
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.testapp.ActivityOptions.LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME
-import com.android.server.wm.flicker.testapp.ActivityOptions.SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME
+import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
 import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -105,7 +104,7 @@
     }
 
     /**
-     * Check that the [ComponentMatcher.LAUNCHER] window is never visible when performing task
+     * Check that the [ComponentNameMatcher.LAUNCHER] window is never visible when performing task
      * transitions.
      * A solid color background should be shown above it.
      */
@@ -118,7 +117,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.LAUNCHER] layer is never visible when performing task
+     * Checks that the [ComponentNameMatcher.LAUNCHER] layer is never visible when performing task
      * transitions.
      * A solid color background should be shown above it.
      */
@@ -246,8 +245,8 @@
 
     companion object {
         private val LAUNCH_NEW_TASK_ACTIVITY =
-            LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
-        private val SIMPLE_ACTIVITY = SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
+            ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+        private val SIMPLE_ACTIVITY = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
 
         private fun getWallpaperPackage(instrumentation: Instrumentation): IComponentMatcher? {
             val wallpaperManager = WallpaperManager.getInstance(instrumentation.targetContext)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 16ad630..9d27079 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -90,7 +90,7 @@
                 testApp.launchViaIntent(
                     wmHelper,
                     stringExtras = mapOf(
-                        ActivityOptions.EXTRA_STARVE_UI_THREAD
+                        ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD
                             to testSpec.starveUiThread.toString()
                     )
                 )
@@ -164,20 +164,23 @@
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarLayerPositionAtStartAndEnd() { }
+    override fun statusBarLayerPositionAtStartAndEnd() {
+    }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarLayerIsVisibleAtStartAndEnd() { }
+    override fun statusBarLayerIsVisibleAtStartAndEnd() {
+    }
 
     /** {@inheritDoc} */
     @Test
     @Ignore("Not applicable to this CUJ. App is full screen")
-    override fun statusBarWindowIsAlwaysVisible() { }
+    override fun statusBarWindowIsAlwaysVisible() {
+    }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] window is invisible during the whole
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] window is invisible during the whole
      * transition
      */
     @Presubmit
@@ -189,7 +192,7 @@
     }
 
     /**
-     * Checks that the [ComponentMatcher.STATUS_BAR] layer is invisible during the whole
+     * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is invisible during the whole
      * transition
      */
     @Presubmit
@@ -218,14 +221,17 @@
 
     companion object {
         private val FlickerTestParameter.starveUiThread
-            get() = config.getOrDefault(ActivityOptions.EXTRA_STARVE_UI_THREAD, false) as Boolean
+            get() = config.getOrDefault(
+                ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false) as Boolean
 
         private fun createConfig(
             sourceConfig: FlickerTestParameter,
             starveUiThread: Boolean
         ): FlickerTestParameter {
             val newConfig = sourceConfig.config.toMutableMap()
-                .also { it[ActivityOptions.EXTRA_STARVE_UI_THREAD] = starveUiThread }
+                .also {
+                    it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
+                }
             val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
             return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
         }
@@ -233,8 +239,8 @@
         /**
          * Creates the test configurations for seamless rotation based on the default rotation
          * tests from [FlickerTestParameterFactory.getConfigRotationTests], but adding an
-         * additional flag ([ActivityOptions.EXTRA_STARVE_UI_THREAD]) to indicate if the app
-         * should starve the UI thread of not
+         * additional flag ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate
+         * if the app should starve the UI thread of not
          */
         @JvmStatic
         private fun getConfigurations(): List<FlickerTestParameter> {
diff --git a/tests/FlickerTests/test-apps/Android.bp b/tests/FlickerTests/test-apps/Android.bp
deleted file mode 100644
index e69de29..0000000
--- a/tests/FlickerTests/test-apps/Android.bp
+++ /dev/null
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 725963b..6e935d1 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -15,41 +15,41 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.server.wm.flicker.testapp">
+          package="com.android.server.wm.flicker.testapp">
 
     <uses-sdk android:minSdkVersion="29"
-         android:targetSdkVersion="29"/>
+              android:targetSdkVersion="29"/>
     <application android:allowBackup="false"
-         android:supportsRtl="true">
+                 android:supportsRtl="true">
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
 
         <activity android:name=".SimpleActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:label="SimpleApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SimpleActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SimpleActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ImeActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:label="ImeApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="ImeActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ImeActivityAutoFocus"
-             android:theme="@style/CutoutShortEdges"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
-             android:windowSoftInputMode="stateVisible"
-             android:configChanges="orientation|screenSize"
-             android:label="ImeAppAutoFocus"
-             android:exported="true">
+                  android:theme="@style/CutoutShortEdges"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
+                  android:windowSoftInputMode="stateVisible"
+                  android:configChanges="orientation|screenSize"
+                  android:label="ImeAppAutoFocus"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -66,34 +66,34 @@
             </intent-filter>
         </activity>
         <activity android:name=".SeamlessRotationActivity"
-             android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
-             android:theme="@style/CutoutShortEdges"
-             android:configChanges="orientation|screenSize"
-             android:label="SeamlessApp"
-             android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="SeamlessActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".NonResizeableActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:resizeableActivity="false"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
-            android:label="NonResizeableApp"
-            android:exported="true"
-            android:showOnLockScreen="true">
+                  android:theme="@style/CutoutShortEdges"
+                  android:resizeableActivity="false"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableActivity"
+                  android:label="NonResizeableActivity"
+                  android:exported="true"
+                  android:showOnLockScreen="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".ButtonActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ButtonActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:label="ButtonActivity"
-            android:exported="true">
+        <activity android:name=".LaunchNewActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchNewActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="LaunchNewActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -111,55 +111,56 @@
             </intent-filter>
         </activity>
         <activity android:name=".DialogThemedActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
-            android:configChanges="orientation|screenSize"
-            android:theme="@style/DialogTheme"
-            android:label="DialogThemedActivity"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:theme="@style/DialogTheme"
+                  android:label="DialogThemedActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".PortraitOnlyActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:screenOrientation="portrait"
-            android:configChanges="orientation|screenSize"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.PortraitOnlyActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:screenOrientation="portrait"
+                  android:configChanges="orientation|screenSize"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ImeEditorPopupDialogActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
-            android:configChanges="orientation|screenSize"
-            android:theme="@style/CutoutShortEdges"
-            android:label="ImeEditorPopupDialogActivity"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ImeEditorPopupDialogActivity"
+                  android:configChanges="orientation|screenSize"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="ImeEditorPopupDialogActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".ShowWhenLockedActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:label="ShowWhenLockedActivity"
-            android:showWhenLocked="true"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.ShowWhenLockedActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="ShowWhenLockedActivity"
+                  android:showWhenLocked="true"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".NotificationActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
-            android:theme="@style/CutoutShortEdges"
-            android:configChanges="orientation|screenSize"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.NotificationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:configChanges="orientation|screenSize"
+                  android:label="NotificationActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
@@ -173,8 +174,8 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity
@@ -193,42 +194,110 @@
             android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
             android:exported="false"/>
         <activity android:name=".MailActivity"
-            android:exported="true"
-            android:label="MailApp"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
-            android:theme="@style/Theme.AppCompat.Light">
+                  android:exported="true"
+                  android:label="MailActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity"
+                  android:theme="@style/Theme.AppCompat.Light">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
         <activity android:name=".GameActivity"
-            android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
-            android:immersive="true"
-            android:theme="@android:style/Theme.NoTitleBar"
-            android:configChanges="screenSize"
-            android:label="GameApp"
-            android:exported="true">
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity"
+                  android:immersive="true"
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:configChanges="screenSize"
+                  android:label="GameActivity"
+                  android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".PipActivity"
+                  android:resizeableActivity="true"
+                  android:supportsPictureInPicture="true"
+                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.PipActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:launchMode="singleTop"
+                  android:label="PipActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SplitScreenActivity"
+                  android:resizeableActivity="true"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SplitScreenPrimaryActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SplitScreenSecondaryActivity"
+                  android:resizeableActivity="true"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SplitScreenSecondaryActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".SendNotificationActivity"
+                  android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
+                  android:theme="@style/CutoutShortEdges"
+                  android:label="SendNotificationActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".LaunchBubbleActivity"
+            android:label="LaunchBubbleActivity"
+            android:exported="true"
+            android:theme="@style/CutoutShortEdges"
+            android:launchMode="singleTop">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".BubbleActivity"
+            android:label="BubbleActivity"
+            android:exported="false"
+            android:theme="@style/CutoutShortEdges"
+            android:resizeableActivity="true"/>
         <service
             android:name=".AssistantInteractionSessionService"
             android:exported="true"
-            android:permission="android.permission.BIND_VOICE_INTERACTION" />
+            android:permission="android.permission.BIND_VOICE_INTERACTION"/>
         <service
             android:name=".AssistantRecognitionService"
             android:exported="true"
             android:label="Test Voice Interaction Service">
             <intent-filter>
-                <action android:name="android.speech.RecognitionService" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.speech.RecognitionService"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <meta-data
                 android:name="android.speech"
-                android:resource="@xml/recognition_service" />
+                android:resource="@xml/recognition_service"/>
         </service>
         <service
             android:name=".AssistantInteractionService"
@@ -236,12 +305,12 @@
             android:label="Test Voice Interaction Service"
             android:permission="android.permission.BIND_VOICE_INTERACTION">
             <intent-filter>
-                <action android:name="android.service.voice.VoiceInteractionService" />
+                <action android:name="android.service.voice.VoiceInteractionService"/>
             </intent-filter>
             <meta-data
                 android:name="android.voice_interaction"
-                android:resource="@xml/interaction_service" />
+                android:resource="@xml/interaction_service"/>
         </service>
     </application>
-    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
 </manifest>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png b/tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
similarity index 100%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/drawable/bg.png
rename to tests/FlickerTests/test-apps/flickerapp/res/drawable/bg.png
Binary files differ
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
new file mode 100644
index 0000000..4ea156d
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_bubble.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M7.2,14.4m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M14.8,18m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M15.2,8.8m-4.8,0a4.8,4.8 0,1 1,9.6 0a4.8,4.8 0,1 1,-9.6 0"/>
+</vector>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
new file mode 100644
index 0000000..45ed98c
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/ic_message.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M12,4c-4.97,0 -9,3.58 -9,8c0,1.53 0.49,2.97 1.33,4.18c0.12,0.18 0.2,0.46 0.1,0.66c-0.33,0.68 -0.79,1.52 -1.38,2.39c-0.12,0.17 0.01,0.41 0.21,0.39c0.63,-0.05 1.86,-0.26 3.38,-0.91c0.17,-0.07 0.36,-0.06 0.52,0.03C8.55,19.54 10.21,20 12,20c4.97,0 9,-3.58 9,-8S16.97,4 12,4zM16.94,11.63l-3.29,3.29c-0.13,0.13 -0.34,0.04 -0.34,-0.14v-1.57c0,-0.11 -0.1,-0.21 -0.21,-0.2c-2.19,0.06 -3.65,0.65 -5.14,1.95c-0.15,0.13 -0.38,0 -0.33,-0.19c0.7,-2.57 2.9,-4.57 5.5,-4.75c0.1,-0.01 0.18,-0.09 0.18,-0.19V8.2c0,-0.18 0.22,-0.27 0.34,-0.14l3.29,3.29C17.02,11.43 17.02,11.55 16.94,11.63z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
similarity index 65%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
index f8b0ca3..7c7b2ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_bubble.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_bubble.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
- Copyright 2021 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.
--->
+  ~ 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.
+  -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
similarity index 100%
rename from tests/FlickerTests/test-apps/flickerapp/res/layout/activity_button.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_launch_new.xml
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
similarity index 67%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
index f23c464..553c7fe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_main.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_main.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
- Copyright 2021 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.
--->
+  ~ 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.
+  -->
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml
new file mode 100644
index 0000000..7c11984
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_notification.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/black">
+
+        <Button
+            android:id="@+id/button_send_notification"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="true"
+            android:text="Send Notification" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
similarity index 87%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
rename to tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
index 2290983..f7ba45b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
- Copyright 2020 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.
--->
+  ~ 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.
+  -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
new file mode 100644
index 0000000..79e88e4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/holo_green_light">
+
+    <TextView
+        android:id="@+id/SplitScreenTest"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical|center_horizontal"
+        android:textIsSelectable="true"
+        android:text="PrimaryActivity"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
new file mode 100644
index 0000000..ed9feaf
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_splitscreen_secondary.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/holo_blue_light">
+
+    <TextView
+        android:id="@+id/SplitScreenTest"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:gravity="center_vertical|center_horizontal"
+        android:text="SecondaryActivity"
+        android:textAppearance="?android:attr/textAppearanceLarge"/>
+
+</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 166e3ca..04a590d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -16,9 +16,6 @@
 
 package com.android.server.wm.flicker.testapp;
 
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME;
-import static com.android.server.wm.flicker.testapp.ActivityOptions.ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME;
-
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
@@ -39,8 +36,6 @@
     private static final String TAG = "ActivityEmbeddingMainActivity";
     private static final float DEFAULT_SPLIT_RATIO = 0.5f;
 
-    private ActivityEmbeddingComponent mEmbeddingComponent;
-
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -51,20 +46,24 @@
 
     /** R.id.launch_placeholder_split_button onClick */
     public void launchPlaceholderSplit(View view) {
-        startActivity(new Intent().setComponent(
-                ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME));
+        startActivity(
+                new Intent().setComponent(
+                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
+                )
+        );
     }
 
     private void initializeSplitRules() {
-        mEmbeddingComponent = ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
-        if (mEmbeddingComponent == null) {
+        ActivityEmbeddingComponent embeddingComponent =
+                ActivityEmbeddingAppHelper.getActivityEmbeddingComponent();
+        if (embeddingComponent == null) {
             // Embedding not supported
             Log.d(TAG, "ActivityEmbedding is not supported on this device");
             finish();
             return;
         }
 
-        mEmbeddingComponent.setEmbeddingRules(getSplitRules());
+        embeddingComponent.setEmbeddingRules(getSplitRules());
     }
 
     private Set<EmbeddingRule> getSplitRules() {
@@ -72,10 +71,10 @@
 
         final SplitPlaceholderRule placeholderRule = new SplitPlaceholderRule.Builder(
                 new Intent().setComponent(
-                        ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME),
+                        ActivityOptions.ActivityEmbedding.PlaceholderSecondaryActivity.COMPONENT),
                 activity -> activity instanceof ActivityEmbeddingPlaceholderPrimaryActivity,
                 intent -> intent.getComponent().equals(
-                        ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME),
+                        ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT),
                 windowMetrics -> true)
                 .setSplitRatio(DEFAULT_SPLIT_RATIO)
                 .build();
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index cae3df4..9583f97 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -19,94 +19,181 @@
 import android.content.ComponentName;
 
 public class ActivityOptions {
-    public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
     public static final String FLICKER_APP_PACKAGE = "com.android.server.wm.flicker.testapp";
 
-    public static final String SEAMLESS_ACTIVITY_LAUNCHER_NAME = "SeamlessApp";
-    public static final ComponentName SEAMLESS_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
+    public static class SimpleActivity {
+        public static final String LABEL = "SimpleActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".SimpleActivity");
+    }
 
-    public static final String IME_ACTIVITY_AUTO_FOCUS_LAUNCHER_NAME = "ImeAppAutoFocus";
-    public static final ComponentName IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+    public static class SeamlessRotation {
+        public static final String LABEL = "SeamlessRotationActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".SeamlessRotationActivity");
 
-    public static final String IME_ACTIVITY_LAUNCHER_NAME = "ImeActivity";
-    public static final ComponentName IME_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static final String EXTRA_STARVE_UI_THREAD = "StarveUiThread";
+    }
+
+    public static class Ime {
+        public static class Default {
+            public static final String LABEL = "ImeActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeActivity");
+        }
 
-    public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity";
-    public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static class AutoFocusActivity {
+            public static final String LABEL = "ImeAppAutoFocus";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ImeActivityAutoFocus");
+        }
+
+        public static class StateInitializeActivity {
+            public static final String LABEL = "ImeStateInitializeActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity");
+        }
 
-    public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp";
-    public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".SimpleActivity");
-
-    public static final String NON_RESIZEABLE_ACTIVITY_LAUNCHER_NAME = "NonResizeableApp";
-    public static final ComponentName NON_RESIZEABLE_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".NonResizeableActivity");
-
-    public static final String BUTTON_ACTIVITY_LAUNCHER_NAME = "ButtonApp";
-    public static final ComponentName BUTTON_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ButtonActivity");
-
-    public static final String LAUNCH_NEW_TASK_ACTIVITY_LAUNCHER_NAME = "LaunchNewTaskApp";
-    public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
-
-    public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity";
-    public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".DialogThemedActivity");
-
-    public static final String PORTRAIT_ONLY_ACTIVITY_LAUNCHER_NAME = "PortraitOnlyActivity";
-    public static final ComponentName PORTRAIT_ONLY_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
-
-    public static final String EDITOR_POPUP_DIALOG_ACTIVITY_LAUNCHER_NAME =
-            "ImeEditorPopupDialogActivity";
-    public static final ComponentName EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+        public static class EditorPopupDialogActivity {
+            public static final String LABEL = "ImeEditorPopupDialogActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ImeEditorPopupDialogActivity");
+        }
+    }
 
-    public static final String SHOW_WHEN_LOCKED_ACTIVITY_LAUNCHER_NAME = "ShowWhenLockedApp";
-    public static final ComponentName SHOW_WHEN_LOCKED_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+    public static class NonResizeableActivity {
+        public static final String LABEL = "NonResizeableActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".NonResizeableActivity");
+    }
 
-    public static final String NOTIFICATION_ACTIVITY_LAUNCHER_NAME = "NotificationApp";
-    public static final ComponentName NOTIFICATION_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                    FLICKER_APP_PACKAGE + ".NotificationActivity");
+    public static class DialogThemedActivity {
+        public static final String LABEL = "DialogThemedActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".DialogThemedActivity");
+    }
 
-    public static final String ACTIVITY_EMBEDDING_LAUNCHER_NAME = "ActivityEmbeddingMainActivity";
-    public static final ComponentName ACTIVITY_EMBEDDING_MAIN_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
+    public static class PortraitOnlyActivity {
+        public static final String LABEL = "PortraitOnlyActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".PortraitOnlyActivity");
+        public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
+    }
+
+    public static class ActivityEmbedding {
+        public static class MainActivity {
+            public static final String LABEL = "ActivityEmbeddingMainActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
                     FLICKER_APP_PACKAGE + ".ActivityEmbeddingMainActivity");
-    public static final ComponentName
-            ACTIVITY_EMBEDDING_PLACEHOLDER_PRIMARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
-                    FLICKER_APP_PACKAGE,
-            FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
-    public static final ComponentName
-            ACTIVITY_EMBEDDING_PLACEHOLDER_SECONDARY_ACTIVITY_COMPONENT_NAME = new ComponentName(
-                    FLICKER_APP_PACKAGE,
-            FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+        }
 
-    public static final String MAIL_ACTIVITY_LAUNCHER_NAME = "MailActivity";
-    public static final ComponentName MAIL_ACTIVITY_COMPONENT_NAME = new ComponentName(
-            FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".MailActivity");
+        public static class PlaceholderPrimaryActivity {
+            public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderPrimaryActivity");
+        }
 
-    public static final String GAME_ACTIVITY_LAUNCHER_NAME = "GameApp";
-    public static final ComponentName GAME_ACTIVITY_COMPONENT_NAME =
-            new ComponentName(FLICKER_APP_PACKAGE,
-                   FLICKER_APP_PACKAGE + ".GameActivity");
+        public static class PlaceholderSecondaryActivity {
+            public static final String LABEL = "ActivityEmbeddingPlaceholderSecondaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".ActivityEmbeddingPlaceholderSecondaryActivity");
+        }
+    }
+
+    public static class Notification {
+        public static final String LABEL = "NotificationActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".NotificationActivity");
+    }
+
+    public static class Mail {
+        public static final String LABEL = "MailActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".MailActivity");
+    }
+
+    public static class ShowWhenLockedActivity {
+        public static final String LABEL = "ShowWhenLockedActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".ShowWhenLockedActivity");
+    }
+
+    public static class LaunchNewTask {
+        public static final String LABEL = "LaunchNewTaskActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity");
+    }
+
+    public static class Game {
+        public static final String LABEL = "GameActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".GameActivity");
+    }
+
+    public static class LaunchNewActivity {
+        public static final String LABEL = "LaunchNewActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".LaunchNewActivity");
+    }
+
+    public static class Pip {
+        // Test App > Pip Activity
+        public static final String LABEL = "PipActivity";
+        public static final String MENU_ACTION_NO_OP = "No-Op";
+        public static final String MENU_ACTION_ON = "On";
+        public static final String MENU_ACTION_OFF = "Off";
+        public static final String MENU_ACTION_CLEAR = "Clear";
+
+        // Intent action that this activity dynamically registers to enter picture-in-picture
+        public static final String ACTION_ENTER_PIP =
+                FLICKER_APP_PACKAGE + ".PipActivity.ENTER_PIP";
+        // Intent action that this activity dynamically registers to set requested orientation.
+        // Will apply the oriention to the value set in the EXTRA_FIXED_ORIENTATION extra.
+        public static final String ACTION_SET_REQUESTED_ORIENTATION =
+                FLICKER_APP_PACKAGE + ".PipActivity.SET_REQUESTED_ORIENTATION";
+
+        // Calls enterPictureInPicture() on creation
+        public static final String EXTRA_ENTER_PIP = "enter_pip";
+        // Sets the fixed orientation (can be one of {@link ActivityInfo.ScreenOrientation}
+        public static final String EXTRA_PIP_ORIENTATION = "fixed_orientation";
+        // Adds a click listener to finish this activity when it is clicked
+        public static final String EXTRA_TAP_TO_FINISH = "tap_to_finish";
+
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".PipActivity");
+    }
+
+    public static class SplitScreen {
+        public static class Primary {
+            public static final String LABEL = "SplitScreenPrimaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".SplitScreenActivity");
+        }
+
+        public static class Secondary {
+            public static final String LABEL = "SplitScreenSecondaryActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".SplitScreenSecondaryActivity");
+        }
+    }
+
+    public static class SendNotificationActivity {
+        public static final String LABEL = "SendNotificationActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".SendNotificationActivity");
+    }
+
+    public static class Bubbles {
+        public static class LaunchBubble {
+            public static final String LABEL = "LaunchBubbleActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".LaunchBubbleActivity");
+        }
+
+        public static class BubbleActivity {
+            public static final String LABEL = "BubbleActivity";
+            public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                    FLICKER_APP_PACKAGE + ".BubbleActivity");
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
similarity index 94%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
index bc3bc75..58d7e67 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Activity;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
index 6cd93ef..c92b82b 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/BubbleHelper.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Notification;
@@ -86,7 +86,7 @@
 
     }
 
-      private int getNextNotifyId() {
+    private int getNextNotifyId() {
         int id = mNextNotifyId;
         mNextNotifyId++;
         return id;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
similarity index 82%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
index d4ae6c1..722929f 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/FixedActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/FixedActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
-import static com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION;
 
 import android.os.Bundle;
 
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
index 71fa66d..dea3444 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/LaunchBubbleActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 
 import android.app.Activity;
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
similarity index 87%
rename from tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
index b42ac2a..e5710c8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ButtonActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchNewActivity.java
@@ -22,7 +22,7 @@
 import android.view.WindowManager;
 import android.widget.Button;
 
-public class ButtonActivity extends Activity {
+public class LaunchNewActivity extends Activity {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -30,11 +30,11 @@
         p.layoutInDisplayCutoutMode = WindowManager.LayoutParams
                 .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         getWindow().setAttributes(p);
-        setContentView(R.layout.activity_button);
+        setContentView(R.layout.activity_launch_new);
 
         Button button = findViewById(R.id.launch_second_activity);
         button.setOnClickListener(v -> {
-            Intent intent = new Intent(ButtonActivity.this, SimpleActivity.class);
+            Intent intent = new Intent(LaunchNewActivity.this, SimpleActivity.class);
             startActivity(intent);
         });
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
similarity index 95%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
index 615b173..cdb1d42 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import static android.media.MediaMetadata.METADATA_KEY_TITLE;
 import static android.media.session.PlaybackState.ACTION_PAUSE;
@@ -24,10 +24,10 @@
 import static android.media.session.PlaybackState.STATE_PLAYING;
 import static android.media.session.PlaybackState.STATE_STOPPED;
 
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP;
-import static com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_SET_REQUESTED_ORIENTATION;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_ENTER_PIP;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.Pip.EXTRA_PIP_ORIENTATION;
 
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -127,7 +127,6 @@
                         break;
                     default:
                         Log.w(TAG, "Unhandled action=" + intent.getAction());
-                        return;
                 }
             }
         }
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
index 5cf81cb..ce7a005 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SeamlessRotationActivity.java
@@ -18,7 +18,7 @@
 
 import static android.os.SystemClock.sleep;
 
-import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
+import static com.android.server.wm.flicker.testapp.ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
index 8020ef2..8868488 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SendNotificationActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
 import android.app.Notification;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
similarity index 88%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
index 9c82eea..70196ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
similarity index 89%
rename from libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
rename to tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
index baa1e6f..a8ce8ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SplitScreenSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/SplitScreenSecondaryActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.testapp;
+package com.android.server.wm.flicker.testapp;
 
 import android.app.Activity;
 import android.os.Bundle;
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 54b3c40..f924b2e 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -23,6 +23,7 @@
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE;
 import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
 
@@ -276,7 +277,6 @@
     @Test
     public void testSystemReady() throws Exception {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
 
         verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class));
         verify(mSubscriptionTracker).register();
@@ -494,8 +494,10 @@
         mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
 
         triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
-        mTestLooper.dispatchAll();
 
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
         verify(vcn).teardownAsynchronously();
         verify(mMockPolicyListener).onPolicyChanged();
     }
@@ -521,6 +523,92 @@
         assertEquals(0, mVcnMgmtSvc.getAllVcns().size());
     }
 
+    /**
+     * Tests an intermediate state where carrier privileges are marked as lost before active data
+     * subId changes during a SIM ejection.
+     *
+     * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to
+     * immediately.
+     */
+    @Test
+    public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate privileges lost
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                TEST_SUBSCRIPTION_ID,
+                TEST_UUID_2,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+
+        // Verify teardown after delay
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances()
+            throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCbAndGetSnapshot(
+                INVALID_SUBSCRIPTION_ID,
+                null /* activeDataSubscriptionGroup */,
+                Collections.emptySet(),
+                Collections.emptyMap(),
+                false /* hasCarrierPrivileges */);
+
+        // Simulate new SIM loaded right during teardown delay.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+
+        // Verify that even after the full timeout duration, the VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(vcn, never()).teardownAsynchronously();
+    }
+
+    @Test
+    public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception {
+        setupActiveSubscription(TEST_UUID_2);
+
+        final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback();
+        final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Simulate SIM unloaded
+        triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet());
+
+        // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new
+        // vcnInstance.
+        mTestLooper.moveTimeForward(
+                VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
+        mTestLooper.dispatchAll();
+        mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
+        triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2));
+        final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
+
+        // Verify that new instance was different, and the old one was torn down
+        assertTrue(oldInstance != newInstance);
+        verify(oldInstance).teardownAsynchronously();
+
+        // Verify that even after the full timeout duration, the new VCN instance is not torn down
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
+        mTestLooper.dispatchAll();
+        verify(newInstance, never()).teardownAsynchronously();
+    }
+
     @Test
     public void testPackageChangeListenerRegistered() throws Exception {
         verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> {
@@ -910,8 +998,6 @@
     private void setupSubscriptionAndStartVcn(
             int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
-
         triggerSubscriptionTrackerCbAndGetSnapshot(
                 subGrp,
                 Collections.singleton(subGrp),
@@ -1007,7 +1093,6 @@
 
     private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
         mVcnMgmtSvc.systemReady();
-        mTestLooper.dispatchAll();
 
         final ArgumentCaptor<NetworkCallback> captor =
                 ArgumentCaptor.forClass(NetworkCallback.class);
@@ -1252,14 +1337,15 @@
                 true /* isActive */,
                 true /* hasCarrierPrivileges */);
 
-        // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes
-        // inactive.
+        // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown
+        // timeout so the VCN goes inactive.
         final TelephonySubscriptionSnapshot snapshot =
                 triggerSubscriptionTrackerCbAndGetSnapshot(
                         TEST_UUID_1,
                         Collections.singleton(TEST_UUID_1),
                         Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1),
                         false /* hasCarrierPrivileges */);
+        mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
         mTestLooper.dispatchAll();
 
         // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE