Merge "Add Package Specific Verbose Logging" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a5178cf..2623702 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -35,6 +35,7 @@
         ":android.widget.flags-aconfig-java{.generated_srcjars}",
         ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
         ":sdk_sandbox_flags_lib{.generated_srcjars}",
+        ":android.permission.flags-aconfig-java{.generated_srcjars}",
     ],
     // Add aconfig-annotations-lib as a dependency for the optimization
     libs: ["aconfig-annotations-lib"],
@@ -130,7 +131,6 @@
     name: "android.security.flags-aconfig-java-host",
     aconfig_declarations: "android.security.flags-aconfig",
     host_supported: true,
-    test: true,
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
@@ -250,3 +250,16 @@
     aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Permissions
+aconfig_declarations {
+    name: "android.permission.flags-aconfig",
+    package: "android.permission.flags",
+    srcs: ["core/java/android/permission/flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.permission.flags-aconfig-java",
+    aconfig_declarations: "android.permission.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
index 9699757..75838c2 100644
--- a/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
+++ b/apex/jobscheduler/framework/java/android/os/WearModeManagerInternal.java
@@ -49,13 +49,25 @@
     String QUICK_DOZE_REQUEST_IDENTIFIER = "quick_doze_request";
 
     /**
+     * Mode manager off body state identifier.
+     *
+     * <p>Unique identifier that can be used as identifier parameter in
+     * registerInternalStateObserver
+     * to listen to changes in quick doze request state from mode manager.
+     *
+     * TODO(b/288276510): convert to int constant
+     */
+    String OFFBODY_STATE_ID = "off_body";
+
+    /**
      * StringDef for Mode manager identifiers.
      *
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef({
-            QUICK_DOZE_REQUEST_IDENTIFIER
+            QUICK_DOZE_REQUEST_IDENTIFIER,
+            OFFBODY_STATE_ID
     })
     @Target(ElementType.TYPE_USE)
     @interface Identifier {
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index b8596d5..1eaabf5 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -377,7 +377,11 @@
     @GuardedBy("this")
     private boolean mModeManagerRequestedQuickDoze;
     @GuardedBy("this")
+    private boolean mIsOffBody;
+    @GuardedBy("this")
     private boolean mForceModeManagerQuickDozeRequest;
+    @GuardedBy("this")
+    private boolean mForceModeManagerOffBodyState;
 
     /** Time in the elapsed realtime timebase when this listener last received a motion event. */
     @GuardedBy("this")
@@ -437,6 +441,7 @@
     private static final int ACTIVE_REASON_ALARM = 7;
     private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
     private static final int ACTIVE_REASON_MODE_MANAGER = 9;
+    private static final int ACTIVE_REASON_ONBODY = 10;
 
     @VisibleForTesting
     static String stateToString(int state) {
@@ -837,7 +842,7 @@
     class ModeManagerQuickDozeRequestConsumer implements Consumer<Boolean> {
         @Override
         public void accept(Boolean enabled) {
-            Slog.d(TAG, "Mode manager quick doze request: " + enabled);
+            Slog.i(TAG, "Mode manager quick doze request: " + enabled);
             synchronized (DeviceIdleController.this) {
                 if (!mForceModeManagerQuickDozeRequest
                         && mModeManagerRequestedQuickDoze != enabled) {
@@ -848,13 +853,46 @@
         }
 
         @GuardedBy("DeviceIdleController.this")
-        public void onModeManagerRequestChangedLocked() {
+        private void onModeManagerRequestChangedLocked() {
             // Get into quick doze faster when mode manager requests instead of taking
             // traditional multi-stage approach.
+            maybeBecomeActiveOnModeManagerEventsLocked();
             updateQuickDozeFlagLocked();
-            if (!mModeManagerRequestedQuickDoze && !mBatterySaverEnabled) {
-                mActiveReason = ACTIVE_REASON_MODE_MANAGER;
-                becomeActiveLocked("mode_manager", Process.myUid());
+        }
+    }
+
+    @VisibleForTesting
+    class ModeManagerOffBodyStateConsumer implements Consumer<Boolean> {
+        @Override
+        public void accept(Boolean isOffBody) {
+            Slog.i(TAG, "Offbody event from mode manager: " + isOffBody);
+            synchronized (DeviceIdleController.this) {
+                if (!mForceModeManagerOffBodyState && mIsOffBody != isOffBody) {
+                    mIsOffBody = isOffBody;
+                    onModeManagerOffBodyChangedLocked();
+                }
+            }
+        }
+
+        @GuardedBy("DeviceIdleController.this")
+        private void onModeManagerOffBodyChangedLocked() {
+            maybeBecomeActiveOnModeManagerEventsLocked();
+        }
+    }
+
+    @GuardedBy("DeviceIdleController.this")
+    private void maybeBecomeActiveOnModeManagerEventsLocked() {
+        synchronized (DeviceIdleController.this) {
+            if (mQuickDozeActivated) {
+                // Quick doze is enabled so don't turn the device active.
+                return;
+            }
+            // Fall through when quick doze is not requested.
+
+            if (!mIsOffBody) {
+                // Quick doze was not requested and device is on body so turn the device active.
+                mActiveReason = ACTIVE_REASON_ONBODY;
+                becomeActiveLocked("on_body", Process.myUid());
             }
         }
     }
@@ -864,6 +902,10 @@
             new ModeManagerQuickDozeRequestConsumer();
 
     @VisibleForTesting
+    final ModeManagerOffBodyStateConsumer mModeManagerOffBodyStateConsumer =
+            new ModeManagerOffBodyStateConsumer();
+
+    @VisibleForTesting
     final class MotionListener extends TriggerEventListener
             implements SensorEventListener {
 
@@ -2648,6 +2690,12 @@
                                 WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER,
                                 AppSchedulingModuleThread.getExecutor(),
                                 mModeManagerQuickDozeRequestConsumer);
+
+                        modeManagerInternal.addActiveStateChangeListener(
+                                WearModeManagerInternal.OFFBODY_STATE_ID,
+                                AppSchedulingModuleThread.getExecutor(),
+                                mModeManagerOffBodyStateConsumer
+                        );
                     }
                 }
                 mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE,
@@ -4463,7 +4511,7 @@
         pw.println(
                 "    Resume normal functioning after force-idle or force-inactive or "
                         + "force-modemanager-quickdoze.");
-        pw.println("  get [light|deep|force|screen|charging|network|offbody|forcebodystate]");
+        pw.println("  get [light|deep|force|screen|charging|network|offbody|forceoffbody]");
         pw.println("    Retrieve the current given state.");
         pw.println("  disable [light|deep|all]");
         pw.println("    Completely disable device idle mode.");
@@ -4500,6 +4548,10 @@
         pw.println("  force-modemanager-quickdoze [true|false]");
         pw.println("    Simulate mode manager request to enable (true) or disable (false) "
                 + "quick doze. Mode manager changes will be ignored until unforce is called.");
+        pw.println("  force-modemanager-offbody [true|false]");
+        pw.println("    Force mode manager offbody state, this can be used to simulate "
+                + "device being off-body (true) or on-body (false). Mode manager changes "
+                + "will be ignored until unforce is called.");
     }
 
     class Shell extends ShellCommand {
@@ -4634,6 +4686,9 @@
                     mForceModeManagerQuickDozeRequest = false;
                     pw.println("mForceModeManagerQuickDozeRequest: "
                             + mForceModeManagerQuickDozeRequest);
+                    mForceModeManagerOffBodyState = false;
+                    pw.println("mForceModeManagerOffBodyState: "
+                            + mForceModeManagerOffBodyState);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -4660,6 +4715,8 @@
                             case "forcemodemanagerquick":
                                 pw.println(mForceModeManagerQuickDozeRequest);
                                 break;
+                            case "offbody": pw.println(mIsOffBody); break;
+                            case "forceoffbody": pw.println(mForceModeManagerOffBodyState); break;
                             default: pw.println("Unknown get option: " + arg); break;
                         }
                     } finally {
@@ -4982,6 +5039,31 @@
                 pw.println("Provide true or false argument after force-modemanager-quickdoze");
                 return -1;
             }
+        } else if ("force-modemanager-offbody".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            String arg = shell.getNextArg();
+
+            if ("true".equalsIgnoreCase(arg) || "false".equalsIgnoreCase(arg)) {
+                boolean isOffBody = Boolean.parseBoolean(arg);
+
+                synchronized (DeviceIdleController.this) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mForceModeManagerOffBodyState = true;
+                        pw.println("mForceModeManagerOffBodyState: "
+                                + mForceModeManagerOffBodyState);
+                        mIsOffBody = isOffBody;
+                        pw.println("mIsOffBody: " + mIsOffBody);
+                        mModeManagerOffBodyStateConsumer.onModeManagerOffBodyChangedLocked();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } else {
+                pw.println("Provide true or false argument after force-modemanager-offbody");
+                return -1;
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -5233,6 +5315,12 @@
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
+            if (mConstants.USE_MODE_MANAGER) {
+                pw.print("  mModeManagerRequestedQuickDoze=");
+                pw.println(mModeManagerRequestedQuickDoze);
+                pw.print("  mIsOffBody");
+                pw.println(mIsOffBody);
+            }
         }
     }
 
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index 9225fe8..71f366a 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
 
 # Convert each removed.txt to the "dex format" equivalent, and print all output.
 for f in "$@"; do
-    "$metalava_path" --no-banner "$f" --dex-api "${tmp_dir}/tmp"
+    "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp"
     cat "${tmp_dir}/tmp"
 done
diff --git a/core/api/current.txt b/core/api/current.txt
index f488c82..a5784a0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32285,6 +32285,7 @@
     method public static long getNativeHeapFreeSize();
     method public static long getNativeHeapSize();
     method public static long getPss();
+    method @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) public static long getRss();
     method public static String getRuntimeStat(String);
     method public static java.util.Map<java.lang.String,java.lang.String> getRuntimeStats();
     method @Deprecated public static int getThreadAllocCount();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c9db763..5c48b21 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -95,6 +95,7 @@
     field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
     field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
+    field public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
     field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
@@ -3483,6 +3484,7 @@
     field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
     field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
     field public static final String TETHERING_SERVICE = "tethering";
+    field public static final String THREAD_NETWORK_SERVICE = "thread_network";
     field public static final String TIME_MANAGER_SERVICE = "time_manager";
     field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
     field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
@@ -3798,10 +3800,6 @@
     field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
   }
 
-  public class PackageInfo implements android.os.Parcelable {
-    field public boolean isArchived;
-  }
-
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
@@ -3879,6 +3877,7 @@
     method public static void forceSafeLabels();
     method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager);
     method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int);
+    field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived;
   }
 
   public abstract class PackageManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 37162f5..a4cc446 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -50,6 +50,7 @@
     field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
     field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
     field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+    field public static final String START_ACTIVITIES_FROM_SDK_SANDBOX = "android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
@@ -3505,6 +3506,10 @@
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
   }
 
+  public static final class MotionEvent.PointerCoords {
+    method public boolean isResampled();
+  }
+
   @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD}) public @interface RemotableViewMethod {
     method public abstract String asyncImpl() default "";
   }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 00e546a..0191201 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3720,7 +3720,19 @@
     /**  Core implementation of activity launch. */
     private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         ActivityInfo aInfo = r.activityInfo;
-        if (r.packageInfo == null) {
+
+        if (getInstrumentation() != null
+                && getInstrumentation().getContext() != null
+                && getInstrumentation().getContext().getApplicationInfo() != null
+                && getInstrumentation().isSdkSandboxAllowedToStartActivities()) {
+            // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See
+            // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}.
+            r.packageInfo =
+                    getPackageInfo(
+                            getInstrumentation().getContext().getApplicationInfo(),
+                            mCompatibilityInfo,
+                            Context.CONTEXT_INCLUDE_CODE);
+        } else if (r.packageInfo == null) {
             r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
                     Context.CONTEXT_INCLUDE_CODE);
         }
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 0255860..04d04b9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3355,7 +3355,11 @@
         }
         Drawable dr = null;
         if (itemInfo.packageName != null) {
-            dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
+            if (itemInfo.isArchived) {
+                dr = getArchivedAppIcon(itemInfo.packageName);
+            } else {
+                dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
+            }
         }
         if (dr == null && itemInfo != appInfo && appInfo != null) {
             dr = loadUnbadgedItemIcon(appInfo, appInfo);
@@ -3964,4 +3968,14 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Nullable
+    private Drawable getArchivedAppIcon(String packageName) {
+        try {
+            return new BitmapDrawable(null,
+                    mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 0b5a5ed1..1f5f2e4 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -17,7 +17,6 @@
 package android.app;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.annotation.SuppressLint;
@@ -108,13 +107,12 @@
             if (DBG) {
                 Log.d(TAG, "Task#" + i + ": activity=" + task.topActivity
                         + ", visible=" + task.isVisible()
-                        + ", flg=" + Integer.toHexString(task.baseIntent.getFlags()));
+                        + ", flg=" + Integer.toHexString(task.baseIntent.getFlags())
+                        + ", type=" + task.getActivityType());
             }
-            if (!task.isVisible()
-                    || (task.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
-                continue;
+            if (task.isVisible() && (task.getActivityType() == ACTIVITY_TYPE_HOME)) {
+                return true;
             }
-            return task.getActivityType() == ACTIVITY_TYPE_HOME;
         }
         return false;
     }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e31486f..10747bb 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -474,6 +475,56 @@
         sr.waitForComplete();
     }
 
+    boolean isSdkSandboxAllowedToStartActivities() {
+        return Process.isSdkSandbox()
+                && mThread != null
+                && mThread.mBoundApplication != null
+                && mThread.mBoundApplication.isSdkInSandbox
+                && getContext() != null
+                && (getContext()
+                                .checkSelfPermission(
+                                        android.Manifest.permission
+                                                .START_ACTIVITIES_FROM_SDK_SANDBOX)
+                        == PackageManager.PERMISSION_GRANTED);
+    }
+
+    /**
+     * Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents
+     * generated using {@link Context#getPackageName()} use the SDK sandbox package name in the
+     * component field instead of the test package name. An SDK-in-sandbox test attempting to launch
+     * an activity in the test package will encounter name resolution errors when resolving the
+     * activity name in the SDK sandbox package.
+     *
+     * <p>This function replaces the package name of the input intent component to allow activities
+     * belonging to a CTS-in-sandbox test to resolve correctly.
+     *
+     * @param intent the intent to modify to allow CTS-in-sandbox activity resolution.
+     */
+    private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) {
+        if (mComponent != null
+                && intent.getComponent() != null
+                && getContext()
+                        .getPackageManager()
+                        .getSdkSandboxPackageName()
+                        .equals(intent.getComponent().getPackageName())) {
+            // Resolve the intent target for the test package, not for the sandbox package.
+            intent.setComponent(
+                    new ComponentName(
+                            mComponent.getPackageName(), intent.getComponent().getClassName()));
+        }
+        // We match the intent identifier against the running instrumentations for the sandbox.
+        intent.setIdentifier(mComponent.getPackageName());
+    }
+
+    private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) {
+        adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
+        if (ai != null) {
+            ai.processName = mThread.getProcessName();
+        }
+        return ai;
+    }
+
     /**
      * Start a new activity and wait for it to begin running before returning.
      * In addition to being synchronous, this method as some semantic
@@ -531,8 +582,10 @@
         synchronized (mSync) {
             intent = new Intent(intent);
 
-            ActivityInfo ai = intent.resolveActivityInfo(
-                getTargetContext().getPackageManager(), 0);
+            ActivityInfo ai =
+                    isSdkSandboxAllowedToStartActivities()
+                            ? resolveActivityInfoForCtsInSandbox(intent)
+                            : intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
             if (ai == null) {
                 throw new RuntimeException("Unable to resolve activity for: " + intent);
             }
@@ -1842,6 +1895,9 @@
         if (referrer != null) {
             intent.putExtra(Intent.EXTRA_REFERRER, referrer);
         }
+        if (isSdkSandboxAllowedToStartActivities()) {
+            adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
@@ -1914,6 +1970,11 @@
             IBinder token, Activity target, Intent[] intents, Bundle options,
             int userId) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (isSdkSandboxAllowedToStartActivities()) {
+            for (Intent intent : intents) {
+                adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+            }
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
@@ -1989,6 +2050,9 @@
         Context who, IBinder contextThread, IBinder token, String target,
         Intent intent, int requestCode, Bundle options) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (isSdkSandboxAllowedToStartActivities()) {
+            adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
@@ -2060,6 +2124,9 @@
             Context who, IBinder contextThread, IBinder token, String resultWho,
             Intent intent, int requestCode, Bundle options, UserHandle user) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (isSdkSandboxAllowedToStartActivities()) {
+            adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
@@ -2110,6 +2177,9 @@
             Intent intent, int requestCode, Bundle options,
             boolean ignoreTargetSecurity, int userId) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (isSdkSandboxAllowedToStartActivities()) {
+            adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
@@ -2161,6 +2231,9 @@
             Context who, IBinder contextThread, IAppTask appTask,
             Intent intent, Bundle options) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (isSdkSandboxAllowedToStartActivities()) {
+            adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+        }
         if (mActivityMonitors != null) {
             synchronized (mSync) {
                 final int N = mActivityMonitors.size();
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index e447dc5..2d55403 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -49,7 +49,6 @@
 import android.util.Log;
 import android.view.IOnKeyguardExitResult;
 import android.view.IWindowManager;
-import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -71,9 +70,7 @@
 import java.util.concurrent.Executor;
 
 /**
- * Class that can be used to lock and unlock the keyguard. The
- * actual class to control the keyguard locking is
- * {@link android.app.KeyguardManager.KeyguardLock}.
+ * Class to manage and query the state of the lock screen (also known as Keyguard).
  */
 @SystemService(Context.KEYGUARD_SERVICE)
 public class KeyguardManager {
@@ -259,7 +256,9 @@
      * {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
      *
      * @return the intent for launching the activity or null if no password is required.
-     * @deprecated see BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)
+     *
+     * @deprecated see {@link
+     *   android.hardware.biometrics.BiometricPrompt.Builder#setAllowedAuthenticators(int)}
      */
     @Deprecated
     @RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@@ -477,13 +476,12 @@
 
     /**
      * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
-     * you to disable / reenable the keyguard.
+     * you to temporarily disable / reenable the keyguard (lock screen).
      *
-     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
-     * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
-     * instead; this allows you to seamlessly hide the keyguard as your application
-     * moves in and out of the foreground and does not require that any special
-     * permissions be requested.
+     * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+     *   android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly
+     *   occlude and unocclude the keyguard as your application moves in and out of the foreground
+     *   and does not require that any special permissions be requested.
      */
     @Deprecated
     public class KeyguardLock {
@@ -498,12 +496,12 @@
          * Disable the keyguard from showing.  If the keyguard is currently
          * showing, hide it.  The keyguard will be prevented from showing again
          * until {@link #reenableKeyguard()} is called.
-         *
+         * <p>
+         * This only works if the keyguard is not secure.
+         * <p>
          * A good place to call this is from {@link android.app.Activity#onResume()}
          *
-         * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager}
-         * is enabled that requires a password.
-         *
+         * @see KeyguardManager#isKeyguardSecure()
          * @see #reenableKeyguard()
          */
         @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
@@ -520,9 +518,6 @@
          *
          * A good place to call this is from {@link android.app.Activity#onPause()}
          *
-         * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager}
-         * is enabled that requires a password.
-         *
          * @see #disableKeyguard()
          */
         @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
@@ -621,20 +616,18 @@
     }
 
     /**
-     * Enables you to lock or unlock the keyguard. Get an instance of this class by
-     * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
-     * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
+     * Enables you to temporarily disable / reenable the keyguard (lock screen).
+     *
      * @param tag A tag that informally identifies who you are (for debugging who
      *   is disabling the keyguard).
      *
      * @return A {@link KeyguardLock} handle to use to disable and reenable the
      *   keyguard.
      *
-     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
-     *   and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
-     *   instead; this allows you to seamlessly hide the keyguard as your application
-     *   moves in and out of the foreground and does not require that any special
-     *   permissions be requested.
+     * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+     *   android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly
+     *   occlude and unocclude the keyguard as your application moves in and out of the foreground
+     *   and does not require that any special permissions be requested.
      */
     @Deprecated
     public KeyguardLock newKeyguardLock(String tag) {
@@ -642,9 +635,36 @@
     }
 
     /**
-     * Return whether the keyguard is currently locked.
+     * Returns whether the lock screen (also known as Keyguard) is showing.
+     * <p>
+     * Specifically, this returns {@code true} in the following cases:
+     * <ul>
+     *   <li>The lock screen is showing in the foreground.</li>
+     *   <li>The lock screen is showing, but it is occluded by an activity that is showing on top of
+     *   it. A common example is the phone app receiving a call or making an emergency call.</li>
+     *   <li>The lock screen was showing but is temporarily disabled as a result of <a
+     *   href="https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode">lock task
+     *   mode</a> or an app using the deprecated {@link KeyguardLock} API.</li>
+     * </ul>
+     * <p>
+     * "Showing" refers to a logical state of the UI, regardless of whether the screen happens to be
+     * on. When the power button is pressed on an unlocked device, the lock screen starts "showing"
+     * immediately when the screen turns off.
+     * <p>
+     * This method does not distinguish a lock screen that is requiring authentication (e.g. with
+     * PIN, pattern, password, or biometric) from a lock screen that is trivially dismissible (e.g.
+     * with swipe). It also does not distinguish a lock screen requesting a SIM card PIN from a
+     * normal device lock screen. Finally, it always returns the global lock screen state and does
+     * not consider the {@link Context}'s user specifically.
+     * <p>
+     * Note that {@code isKeyguardLocked()} is confusingly named and probably should be called
+     * {@code isKeyguardShowing()}. On many devices, the lock screen displays an <i>unlocked</i>
+     * padlock icon when it is trivially dismissible. As mentioned above, {@code isKeyguardLocked()}
+     * actually returns {@code true} in this case, not {@code false} as might be expected. {@link
+     * #isDeviceLocked()} is an alternative API that has slightly different semantics.
      *
-     * @return {@code true} if the keyguard is locked.
+     * @return {@code true} if the lock screen is showing
+     * @see #isDeviceLocked()
      */
     public boolean isKeyguardLocked() {
         try {
@@ -655,12 +675,23 @@
     }
 
     /**
-     * Return whether the keyguard is secured by a PIN, pattern or password or a SIM card
-     * is currently locked.
+     * Returns whether the user has a secure lock screen or there is a locked SIM card.
+     * <p>
+     * Specifically, this returns {@code true} if at least one of the following is true:
+     * <ul>
+     *   <li>The {@link Context}'s user has a secure lock screen. A full user has a secure lock
+     *   screen if its lock screen is set to PIN, pattern, or password, as opposed to swipe or none.
+     *   A profile that uses a unified challenge is considered to have a secure lock screen if and
+     *   only if its parent user has a secure lock screen.</li>
+     *   <li>At least one SIM card is currently locked and requires a PIN.</li>
+     * </ul>
+     * <p>
+     * This method does not consider whether the lock screen is currently showing or not.
+     * <p>
+     * See also {@link #isDeviceSecure()} which excludes locked SIM cards.
      *
-     * <p>See also {@link #isDeviceSecure()} which ignores SIM locked states.
-     *
-     * @return {@code true} if a PIN, pattern or password is set or a SIM card is locked.
+     * @return {@code true} if the user has a secure lock screen or there is a locked SIM card
+     * @see #isDeviceSecure()
      */
     public boolean isKeyguardSecure() {
         try {
@@ -671,11 +702,11 @@
     }
 
     /**
-     * If keyguard screen is showing or in restricted key input mode (i.e. in
-     * keyguard password emergency screen). When in such mode, certain keys,
-     * such as the Home key and the right soft keys, don't work.
+     * Returns whether the lock screen is showing.
+     * <p>
+     * This is exactly the same as {@link #isKeyguardLocked()}.
      *
-     * @return {@code true} if in keyguard restricted input mode.
+     * @return the value of {@link #isKeyguardLocked()}
      * @deprecated Use {@link #isKeyguardLocked()} instead.
      */
     public boolean inKeyguardRestrictedInputMode() {
@@ -683,11 +714,26 @@
     }
 
     /**
-     * Returns whether the device is currently locked and requires a PIN, pattern or
-     * password to unlock.
+     * Returns whether the device is currently locked for the user.
+     * <p>
+     * This returns the device locked state for the {@link Context}'s user. If this user is the
+     * current user, then the device is considered "locked" when the lock screen is showing (i.e.
+     * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
+     * swipe), and the user has a PIN, pattern, or password.
+     * <p>
+     * Note: the above definition implies that a user with no PIN, pattern, or password is never
+     * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
+     * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
+     * authentication has just completed or a trust agent is keeping the device unlocked, since in
+     * these cases the lock screen is dismissible with swipe.
+     * <p>
+     * For a user that is not the current user but can be switched to (usually this means "another
+     * full user"), and that has a PIN, pattern, or password, the device is always considered
+     * locked. For a profile with a unified challenge, the device is considered locked if and only
+     * if the device is locked for the parent user.
      *
-     * @return {@code true} if unlocking the device currently requires a PIN, pattern or
-     * password.
+     * @return {@code true} if the device is currently locked for the user
+     * @see #isKeyguardLocked()
      */
     public boolean isDeviceLocked() {
         return isDeviceLocked(mContext.getUserId());
@@ -708,12 +754,19 @@
     }
 
     /**
-     * Returns whether the device is secured with a PIN, pattern or
-     * password.
+     * Returns whether the user has a secure lock screen.
+     * <p>
+     * This returns {@code true} if the {@link Context}'s user has a secure lock screen. A full user
+     * has a secure lock screen if its lock screen is set to PIN, pattern, or password, as opposed
+     * to swipe or none. A profile that uses a unified challenge is considered to have a secure lock
+     * screen if and only if its parent user has a secure lock screen.
+     * <p>
+     * This method does not consider whether the lock screen is currently showing or not.
+     * <p>
+     * See also {@link #isKeyguardSecure()} which includes locked SIM cards.
      *
-     * <p>See also {@link #isKeyguardSecure} which treats SIM locked states as secure.
-     *
-     * @return {@code true} if a PIN, pattern or password was set.
+     * @return {@code true} if the user has a secure lock screen
+     * @see #isKeyguardSecure()
      */
     public boolean isDeviceSecure() {
         return isDeviceSecure(mContext.getUserId());
@@ -734,8 +787,7 @@
     }
 
     /**
-     * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
-     * be dismissed.
+     * Requests that the Keyguard (lock screen) be dismissed if it is currently showing.
      * <p>
      * If the Keyguard is not secure or the device is currently in a trusted state, calling this
      * method will immediately dismiss the Keyguard without any user interaction.
@@ -746,8 +798,9 @@
      * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
      * the screen will turn on when the keyguard is dismissed.
      *
-     * @param activity The activity requesting the dismissal. The activity must be either visible
-     *                 by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+     * @param activity The activity requesting the dismissal. The activity must either be visible
+     *                 by using {@link android.R.attr#showWhenLocked} or {@link
+     *                 android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in
      *                 which it would be visible if Keyguard would not be hiding it. If that's not
      *                 the case, the request will fail immediately and
      *                 {@link KeyguardDismissCallback#onDismissError} will be invoked.
@@ -762,8 +815,7 @@
     }
 
     /**
-     * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
-     * be dismissed.
+     * Requests that the Keyguard (lock screen) be dismissed if it is currently showing.
      * <p>
      * If the Keyguard is not secure or the device is currently in a trusted state, calling this
      * method will immediately dismiss the Keyguard without any user interaction.
@@ -774,8 +826,9 @@
      * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
      * the screen will turn on when the keyguard is dismissed.
      *
-     * @param activity The activity requesting the dismissal. The activity must be either visible
-     *                 by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+     * @param activity The activity requesting the dismissal. The activity must either be visible
+     *                 by using {@link android.R.attr#showWhenLocked} or {@link
+     *                 android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in
      *                 which it would be visible if Keyguard would not be hiding it. If that's not
      *                 the case, the request will fail immediately and
      *                 {@link KeyguardDismissCallback#onDismissError} will be invoked.
@@ -829,12 +882,12 @@
      * @param callback Lets you know whether the operation was successful and
      *   it is safe to launch anything that would normally be considered safe
      *   once the user has gotten past the keyguard.
-
-     * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
-     *   and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
-     *   instead; this allows you to seamlessly hide the keyguard as your application
-     *   moves in and out of the foreground and does not require that any special
-     *   permissions be requested.
+     *
+     * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+     *   android.app.Activity#setShowWhenLocked(boolean)} to seamlessly occlude and unocclude the
+     *   keyguard as your application moves in and out of the foreground, without requiring any
+     *   special permissions. Use {@link #requestDismissKeyguard(android.app.Activity,
+     *   KeyguardDismissCallback)} to request dismissal of the keyguard.
      */
     @Deprecated
     @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 715edc5..213e5cb 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11806,6 +11806,34 @@
     }
 
     /**
+     * Returns the list of {@link EnforcingAdmin}s who have set this restriction.
+     *
+     * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
+     * behavior the same as before the bug fix for b/192245204.
+     *
+     * <p>This API is only callable by the system UID
+     *
+     * @param userId      The user for whom to retrieve the information.
+     * @param restriction The restriction enforced by admins. It could be any user restriction or
+     *                    policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+     *                    {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+     *
+     * @hide
+     */
+    public @NonNull Set<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,
+            @NonNull String restriction) {
+        if (mService != null) {
+            try {
+                return new HashSet<>(mService.getEnforcingAdminsForRestriction(
+                        userId, restriction));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
      * Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
      * actual package file remain. This function can be called by a device owner, profile owner, or
      * by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/EnforcingAdmin.aidl b/core/java/android/app/admin/EnforcingAdmin.aidl
new file mode 100644
index 0000000..bfbfdbe
--- /dev/null
+++ b/core/java/android/app/admin/EnforcingAdmin.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.admin;
+
+parcelable EnforcingAdmin;
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
index 771794d..7c718f6 100644
--- a/core/java/android/app/admin/EnforcingAdmin.java
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -38,6 +39,11 @@
     private final UserHandle mUserHandle;
 
     /**
+     * @hide
+     */
+    private final ComponentName mComponentName;
+
+    /**
      * Creates an enforcing admin with the given params.
      */
     public EnforcingAdmin(
@@ -46,6 +52,21 @@
         mPackageName = Objects.requireNonNull(packageName);
         mAuthority = Objects.requireNonNull(authority);
         mUserHandle = Objects.requireNonNull(userHandle);
+        mComponentName = null;
+    }
+
+    /**
+     * Creates an enforcing admin with the given params.
+     *
+     * @hide
+     */
+    public EnforcingAdmin(
+            @NonNull String packageName, @NonNull Authority authority,
+            @NonNull UserHandle userHandle, @Nullable ComponentName componentName) {
+        mPackageName = Objects.requireNonNull(packageName);
+        mAuthority = Objects.requireNonNull(authority);
+        mUserHandle = Objects.requireNonNull(userHandle);
+        mComponentName = componentName;
     }
 
     private EnforcingAdmin(Parcel source) {
@@ -53,6 +74,7 @@
         mUserHandle = new UserHandle(source.readInt());
         mAuthority = Objects.requireNonNull(
                 source.readParcelable(Authority.class.getClassLoader()));
+        mComponentName = source.readParcelable(ComponentName.class.getClassLoader());
     }
 
     /**
@@ -86,7 +108,8 @@
         EnforcingAdmin other = (EnforcingAdmin) o;
         return Objects.equals(mPackageName, other.mPackageName)
                 && Objects.equals(mAuthority, other.mAuthority)
-                && Objects.equals(mUserHandle, other.mUserHandle);
+                && Objects.equals(mUserHandle, other.mUserHandle)
+                && Objects.equals(mComponentName, other.mComponentName);
     }
 
     @Override
@@ -97,7 +120,7 @@
     @Override
     public String toString() {
         return "EnforcingAdmin { mPackageName= " + mPackageName + ", mAuthority= " + mAuthority
-                + ", mUserHandle= " + mUserHandle + " }";
+                + ", mUserHandle= " + mUserHandle + ", mComponentName= " + mComponentName + " }";
     }
 
     @Override
@@ -110,6 +133,7 @@
         dest.writeString(mPackageName);
         dest.writeInt(mUserHandle.getIdentifier());
         dest.writeParcelable(mAuthority, flags);
+        dest.writeParcelable(mComponentName, flags);
     }
 
     @NonNull
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 95ec89e..c49b820 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -54,6 +54,7 @@
 import android.telephony.data.ApnSetting;
 import com.android.internal.infra.AndroidFuture;
 import android.app.admin.DevicePolicyState;
+import android.app.admin.EnforcingAdmin;
 
 import java.util.List;
 
@@ -274,6 +275,7 @@
 
     Intent createAdminSupportIntent(in String restriction);
     Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
+    List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,String restriction);
     boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
     boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
 
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index fa0a1f0..8f71fc0 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,4 +1,4 @@
 # Assign bugs to android-enterprise-triage@google.com
-mdb.ae-provisioning-reviews@google.com
+ae-provisioning-reviews@google.com
+petuska@google.com #{LAST_RESORT_SUGGESTION}
 file:EnterprisePlatform_OWNERS
-petuska@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d6dee93..b6a98a5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4089,6 +4089,7 @@
             VIBRATOR_MANAGER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
+            THREAD_NETWORK_SERVICE,
             CONNECTIVITY_SERVICE,
             PAC_PROXY_SERVICE,
             VCN_MANAGEMENT_SERVICE,
@@ -4764,6 +4765,20 @@
 
     /**
      * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link android.net.thread.ThreadNetworkManager}.
+     *
+     * <p>On devices without {@link PackageManager#FEATURE_THREAD_NETWORK} system feature
+     * the {@link #getSystemService(String)} will return {@code null}.
+     *
+     * @see #getSystemService(String)
+     * @see android.net.thread.ThreadNetworkManager
+     * @hide
+     */
+    @SystemApi
+    public static final String THREAD_NETWORK_SERVICE = "thread_network";
+
+    /**
+     * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.net.IpSecManager} for encrypting Sockets or Networks with
      * IPSec.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e9bbed3..7579d99 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12516,6 +12516,7 @@
         return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
     }
 
+    // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal}
     /** @hide */
     public boolean isSandboxActivity(@NonNull Context context) {
         if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 3669817..72aed2d 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 568631
 
-toddke@android.com
-toddke@google.com
 patb@google.com
 zyy@google.com
+jakmcbane@google.com
\ No newline at end of file
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
new file mode 100644
index 0000000..7ab7ed1
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.content.pm;
+
+/** @hide */
+parcelable ArchivedActivityParcel {
+    String title;
+    // PNG compressed bitmaps.
+    byte[] iconBitmap;
+    byte[] monochromeIconBitmap;
+}
diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl
index 573e690..d3cd79e 100644
--- a/core/java/android/content/pm/ArchivedPackageParcel.aidl
+++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.content.pm.ArchivedActivityParcel;
 import android.content.pm.SigningDetails;
 
 /**
@@ -29,9 +30,8 @@
     int versionCode;
     int versionCodeMajor;
     int targetSdkVersion;
-    String backupAllowed;
     String defaultToDeviceProtectedStorage;
     String requestLegacyExternalStorage;
     String userDataFragile;
-    String clearUserDataOnFailedRestoreAllowed;
+    ArchivedActivityParcel[] archivedActivities;
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 556c794..aca88d6 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -20,6 +20,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ArchivedPackageParcel;
@@ -59,7 +60,7 @@
 import android.os.IRemoteCallback;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.content.IntentSender;
+import android.os.UserHandle;
 
 import java.util.Map;
 
@@ -832,5 +833,7 @@
 
     void unregisterPackageMonitorCallback(IRemoteCallback callback);
 
-    ArchivedPackageParcel getArchivedPackage(in String apkPath);
+    ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
+
+    Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
 }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 1fe1923..63c11b7 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -18,9 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.IntentSender;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -490,18 +488,6 @@
      */
     public boolean isActiveApex;
 
-    /**
-     * Whether the package is currently in an archived state.
-     *
-     * <p>Packages can be archived through
-     * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
-     * on the device, but do keep the data directory.
-     * @hide
-     */
-    // TODO(b/278553670) Unhide and update @links before launch.
-    @SystemApi
-    public boolean isArchived;
-
     public PackageInfo() {
     }
 
@@ -589,7 +575,6 @@
         }
         dest.writeBoolean(isApex);
         dest.writeBoolean(isActiveApex);
-        dest.writeBoolean(isArchived);
         dest.restoreAllowSquashing(prevAllowSquashing);
     }
 
@@ -655,6 +640,5 @@
         }
         isApex = source.readBoolean();
         isActiveApex = source.readBoolean();
-        isArchived = source.readBoolean();
     }
 }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 673a8a5..3a9e9bf 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -371,6 +371,13 @@
             "android.content.pm.extra.UNARCHIVE_ALL_USERS";
 
     /**
+     * A list of warnings that occurred during installation.
+     *
+     * @hide
+     */
+    public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index bb978e0..c7091ad 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -21,6 +21,7 @@
 import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM;
 import static android.text.TextUtils.makeSafeForPresentation;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
@@ -173,6 +174,18 @@
      */
     public int showUserIcon;
 
+    /**
+     * Whether the package is currently in an archived state.
+     *
+     * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+     * on the device, but do keep the data directory.
+     * @hide
+     */
+    // TODO(b/278553670) Unhide and update @links before launch.
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public boolean isArchived;
+
     public PackageItemInfo() {
         showUserIcon = UserHandle.USER_NULL;
     }
@@ -189,6 +202,7 @@
         logo = orig.logo;
         metaData = orig.metaData;
         showUserIcon = orig.showUserIcon;
+        isArchived = orig.isArchived;
     }
 
     /**
@@ -442,6 +456,7 @@
         dest.writeBundle(metaData);
         dest.writeInt(banner);
         dest.writeInt(showUserIcon);
+        dest.writeBoolean(isArchived);
     }
 
     /**
@@ -459,6 +474,7 @@
         }
         proto.write(PackageItemInfoProto.ICON, icon);
         proto.write(PackageItemInfoProto.BANNER, banner);
+        proto.write(PackageItemInfoProto.IS_ARCHIVED, isArchived);
         proto.end(token);
     }
 
@@ -473,6 +489,7 @@
         metaData = source.readBundle();
         banner = source.readInt();
         showUserIcon = source.readInt();
+        isArchived = source.readBoolean();
     }
 
     /**
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 149de7e..0333942 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -834,4 +834,11 @@
 
     public abstract V parseServiceAttributes(Resources res,
             String packageName, AttributeSet attrs);
+
+    @VisibleForTesting
+    public void unregisterReceivers() {
+        mContext.unregisterReceiver(mPackageReceiver);
+        mContext.unregisterReceiver(mExternalReceiver);
+        mContext.unregisterReceiver(mUserRemovedReceiver);
+    }
 }
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 159b789..f3194be 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -23,11 +23,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
-import android.os.Build;
 
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
-import com.android.internal.util.XmlUtils;
 
 import java.util.List;
 import java.util.Set;
@@ -142,32 +140,9 @@
     private final boolean mIsSdkLibrary;
 
     /**
-     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
-     *  backups of its data; <code>true</code> otherwise.
+     * Archival install info.
      */
-    private final boolean mBackupAllowed;
-
-    /**
-     * When set, the default data storage directory for this app is pointed at
-     * the device-protected location.
-     */
-    private final boolean mDefaultToDeviceProtectedStorage;
-
-    /**
-     * If {@code true} this app requests full external storage access.
-     */
-    private final boolean mRequestLegacyExternalStorage;
-
-    /**
-     * Indicates whether this application has declared its user data as fragile, causing the
-     * system to prompt the user on whether to keep the user data on uninstall.
-     */
-    private final boolean mUserDataFragile;
-
-    /**
-     * Indicates whether this application's data will be cleared on a failed restore.
-     */
-    private final boolean mClearUserDataOnFailedRestoreAllowed;
+    private final @Nullable ArchivedPackageParcel mArchivedPackage;
 
     public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
             String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
@@ -179,10 +154,7 @@
             String requiredSystemPropertyName, String requiredSystemPropertyValue,
             int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
             Set<String> requiredSplitTypes, Set<String> splitTypes,
-            boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed,
-            boolean backupAllowed, boolean defaultToDeviceProtectedStorage,
-            boolean requestLegacyExternalStorage, boolean userDataFragile,
-            boolean clearUserDataOnFailedRestoreAllowed) {
+            boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
         mPath = path;
         mPackageName = packageName;
         mSplitName = splitName;
@@ -216,11 +188,7 @@
         mRollbackDataPolicy = rollbackDataPolicy;
         mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
         mIsSdkLibrary = isSdkLibrary;
-        mBackupAllowed = backupAllowed;
-        mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage;
-        mRequestLegacyExternalStorage = requestLegacyExternalStorage;
-        mUserDataFragile = userDataFragile;
-        mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed;
+        mArchivedPackage = null;
     }
 
     public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
@@ -257,16 +225,7 @@
         mRollbackDataPolicy = 0;
         mHasDeviceAdminReceiver = false;
         mIsSdkLibrary = false;
-        // @see ParsingPackageUtils#parseBaseAppBasicFlags
-        mBackupAllowed = XmlUtils.convertValueToBoolean(archivedPackage.backupAllowed, true);
-        mDefaultToDeviceProtectedStorage = XmlUtils.convertValueToBoolean(
-                archivedPackage.defaultToDeviceProtectedStorage, false);
-        mRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
-                archivedPackage.requestLegacyExternalStorage,
-                mTargetSdkVersion < Build.VERSION_CODES.Q);
-        mUserDataFragile = XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false);
-        mClearUserDataOnFailedRestoreAllowed = XmlUtils.convertValueToBoolean(
-                archivedPackage.clearUserDataOnFailedRestoreAllowed, true);
+        mArchivedPackage = archivedPackage;
     }
 
     /**
@@ -576,53 +535,18 @@
     }
 
     /**
-     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
-     *  backups of its data; <code>true</code> otherwise.
+     * Archival install info.
      */
     @DataClass.Generated.Member
-    public boolean isBackupAllowed() {
-        return mBackupAllowed;
-    }
-
-    /**
-     * When set, the default data storage directory for this app is pointed at
-     * the device-protected location.
-     */
-    @DataClass.Generated.Member
-    public boolean isDefaultToDeviceProtectedStorage() {
-        return mDefaultToDeviceProtectedStorage;
-    }
-
-    /**
-     * If {@code true} this app requests full external storage access.
-     */
-    @DataClass.Generated.Member
-    public boolean isRequestLegacyExternalStorage() {
-        return mRequestLegacyExternalStorage;
-    }
-
-    /**
-     * Indicates whether this application has declared its user data as fragile, causing the
-     * system to prompt the user on whether to keep the user data on uninstall.
-     */
-    @DataClass.Generated.Member
-    public boolean isUserDataFragile() {
-        return mUserDataFragile;
-    }
-
-    /**
-     * Indicates whether this application's data will be cleared on a failed restore.
-     */
-    @DataClass.Generated.Member
-    public boolean isClearUserDataOnFailedRestoreAllowed() {
-        return mClearUserDataOnFailedRestoreAllowed;
+    public @Nullable ArchivedPackageParcel getArchivedPackage() {
+        return mArchivedPackage;
     }
 
     @DataClass.Generated(
-            time = 1693513509013L,
+            time = 1694792109463L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mBackupAllowed\nprivate final  boolean mDefaultToDeviceProtectedStorage\nprivate final  boolean mRequestLegacyExternalStorage\nprivate final  boolean mUserDataFragile\nprivate final  boolean mClearUserDataOnFailedRestoreAllowed\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mRevisionCode\nprivate final  int mInstallLocation\nprivate final  int mMinSdkVersion\nprivate final  int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final  boolean mFeatureSplit\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mProfileableByShell\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final  boolean mOverlayIsStatic\nprivate final  int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final  int mRollbackDataPolicy\nprivate final  boolean mHasDeviceAdminReceiver\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 066ff689..5f86742 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -40,7 +40,6 @@
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
 
 import libcore.io.IoUtils;
 
@@ -447,13 +446,6 @@
         int overlayPriority = 0;
         int rollbackDataPolicy = 0;
 
-        boolean clearUserDataAllowed = true;
-        boolean backupAllowed = true;
-        boolean defaultToDeviceProtectedStorage = false;
-        String requestLegacyExternalStorage = null;
-        boolean userDataFragile = false;
-        boolean clearUserDataOnFailedRestoreAllowed = true;
-
         String requiredSystemPropertyName = null;
         String requiredSystemPropertyValue = null;
 
@@ -493,22 +485,6 @@
                 useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
                         "useEmbeddedDex", false);
 
-                clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
-                        "allowClearUserDataOnFailedRestore", true);
-                backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
-                        "allowBackup", true);
-                defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue(
-                        ANDROID_RES_NAMESPACE,
-                        "defaultToDeviceProtectedStorage", false);
-                userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
-                        "hasFragileUserData", false);
-                clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue(
-                        ANDROID_RES_NAMESPACE,
-                        "allowClearUserDataOnFailedRestore", true);
-
-                requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
-                        "requestLegacyExternalStorage");
-
                 rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
                         "rollbackDataPolicy", 0);
                 String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
@@ -629,9 +605,6 @@
             return input.skip(message);
         }
 
-        boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
-                requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q);
-
         return input.success(
                 new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                         configForSplit, usesSplitName, isSplitRequired, versionCode,
@@ -641,9 +614,7 @@
                         overlayIsStatic, overlayPriority, requiredSystemPropertyName,
                         requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
                         rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
-                        hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed,
-                        defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage,
-                        userDataFragile, clearUserDataOnFailedRestoreAllowed));
+                        hasDeviceAdminReceiver, isSdkLibrary));
     }
 
     private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index ccef9de..116dd1f 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.PackageInfo;
 import android.content.pm.SigningDetails;
 import android.content.pm.VerifierInfo;
@@ -112,29 +113,11 @@
      * Indicates if this package is a sdk.
      */
     private final boolean mIsSdkLibrary;
+
     /**
-     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
-     *  backups of its data; <code>true</code> otherwise.
+     * Archival install info.
      */
-    private final boolean mBackupAllowed;
-    /**
-     * When set, the default data storage directory for this app is pointed at
-     * the device-protected location.
-     */
-    private final boolean mDefaultToDeviceProtectedStorage;
-    /**
-     * If {@code true} this app requests full external storage access.
-     */
-    private final boolean mRequestLegacyExternalStorage;
-    /**
-     * Indicates whether this application has declared its user data as fragile, causing the
-     * system to prompt the user on whether to keep the user data on uninstall.
-     */
-    private final boolean mUserDataFragile;
-    /**
-     * Indicates whether this application's data will be cleared on a failed restore.
-     */
-    private final boolean mClearUserDataOnFailedRestoreAllowed;
+    private final @Nullable ArchivedPackageParcel mArchivedPackage;
 
     public PackageLite(String path, String baseApkPath, ApkLite baseApk,
             String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -171,11 +154,7 @@
         mSplitApkPaths = splitApkPaths;
         mSplitRevisionCodes = splitRevisionCodes;
         mTargetSdk = targetSdk;
-        mBackupAllowed = baseApk.isBackupAllowed();
-        mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage();
-        mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage();
-        mUserDataFragile = baseApk.isUserDataFragile();
-        mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed();
+        mArchivedPackage = baseApk.getArchivedPackage();
     }
 
     /**
@@ -455,53 +434,18 @@
     }
 
     /**
-     *  Set to <code>false</code> if the application does not wish to permit any OS-driven
-     *  backups of its data; <code>true</code> otherwise.
+     * Archival install info.
      */
     @DataClass.Generated.Member
-    public boolean isBackupAllowed() {
-        return mBackupAllowed;
-    }
-
-    /**
-     * When set, the default data storage directory for this app is pointed at
-     * the device-protected location.
-     */
-    @DataClass.Generated.Member
-    public boolean isDefaultToDeviceProtectedStorage() {
-        return mDefaultToDeviceProtectedStorage;
-    }
-
-    /**
-     * If {@code true} this app requests full external storage access.
-     */
-    @DataClass.Generated.Member
-    public boolean isRequestLegacyExternalStorage() {
-        return mRequestLegacyExternalStorage;
-    }
-
-    /**
-     * Indicates whether this application has declared its user data as fragile, causing the
-     * system to prompt the user on whether to keep the user data on uninstall.
-     */
-    @DataClass.Generated.Member
-    public boolean isUserDataFragile() {
-        return mUserDataFragile;
-    }
-
-    /**
-     * Indicates whether this application's data will be cleared on a failed restore.
-     */
-    @DataClass.Generated.Member
-    public boolean isClearUserDataOnFailedRestoreAllowed() {
-        return mClearUserDataOnFailedRestoreAllowed;
+    public @Nullable ArchivedPackageParcel getArchivedPackage() {
+        return mArchivedPackage;
     }
 
     @DataClass.Generated(
-            time = 1693513525097L,
+            time = 1694792176268L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\nprivate final  boolean mBackupAllowed\nprivate final  boolean mDefaultToDeviceProtectedStorage\nprivate final  boolean mRequestLegacyExternalStorage\nprivate final  boolean mUserDataFragile\nprivate final  boolean mClearUserDataOnFailedRestoreAllowed\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final  int mVersionCodeMajor\nprivate final  int mVersionCode\nprivate final  int mTargetSdk\nprivate final  int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final  int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final  boolean mIsolatedSplits\nprivate final  boolean mSplitRequired\nprivate final  boolean mCoreApp\nprivate final  boolean mDebuggable\nprivate final  boolean mMultiArch\nprivate final  boolean mUse32bitAbi\nprivate final  boolean mExtractNativeLibs\nprivate final  boolean mProfileableByShell\nprivate final  boolean mUseEmbeddedDex\nprivate final  boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic  java.util.List<java.lang.String> getAllApkPaths()\npublic  long getLongVersionCode()\nprivate  boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index a7bce12..141d58d 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -1,8 +1,7 @@
 # Bug component: 568761
 
-toddke@android.com
-toddke@google.com
 patb@google.com
 zyy@google.com
+branliu@google.com
 
 per-file FontScaleConverter*=fuego@google.com
\ No newline at end of file
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index eedb25b..408869e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.Hide;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -117,6 +118,32 @@
     }
 
     /**
+     * Returns a list of candidate credentials returned from credential manager providers
+     *
+     * @param request the request specifying type(s) of credentials to get from the
+     *                credential providers
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    @Hide
+    public void getCandidateCredentials(
+            @NonNull GetCredentialRequest request,
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+        requireNonNull(request, "request must not be null");
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "getCredential already canceled");
+        }
+    }
+
+    /**
      * Launches the necessary flows to retrieve an app credential from the user.
      *
      * <p>The execution can potentially launch UI flows to collect user consent to using a
@@ -641,6 +668,44 @@
         }
     }
 
+    private static class GetCandidateCredentialsTransport
+            extends IGetCandidateCredentialsCallback.Stub {
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<GetCandidateCredentialsResponse,
+                GetCandidateCredentialsException> mCallback;
+
+        private GetCandidateCredentialsTransport(
+                Executor executor,
+                OutcomeReceiver<GetCandidateCredentialsResponse,
+                        GetCandidateCredentialsException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResponse(GetCandidateCredentialsResponse response) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onResult(response));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void onError(String errorType, String message) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(
+                        () -> mCallback.onError(new GetCandidateCredentialsException(
+                                errorType, message)));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
         // TODO: listen for cancellation to release callback.
 
diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java
new file mode 100644
index 0000000..40650d0
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsException.java
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#getCandidateCredentials} operation.
+ *
+ * @hide
+ */
+@Hide
+public class GetCandidateCredentialsException extends Exception {
+    /**
+     * The error type value for when the given operation failed due to an unknown reason.
+     */
+    @NonNull
+    public static final String TYPE_UNKNOWN =
+            "android.credentials.GetCandidateCredentialsException.TYPE_UNKNOWN";
+
+    /**
+     * The error type value for when no credential is found available for the given {@link
+     * CredentialManager#getCandidateCredentials} request.
+     */
+    @NonNull
+    public static final String TYPE_NO_CREDENTIAL =
+            "android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL";
+
+    @NonNull
+    private final String mType;
+
+    /** Returns the specific exception type. */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Constructs a {@link GetCandidateCredentialsException}.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public GetCandidateCredentialsException(@NonNull String type, @Nullable String message) {
+        this(type, message, null);
+    }
+
+    /**
+     * Constructs a {@link GetCandidateCredentialsException}.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public GetCandidateCredentialsException(
+            @NonNull String type, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.mType = Preconditions.checkStringNotEmpty(type,
+                "type must not be empty");
+    }
+
+    /**
+     * Constructs a {@link GetCandidateCredentialsException}.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public GetCandidateCredentialsException(@NonNull String type, @Nullable Throwable cause) {
+        this(type, null, cause);
+    }
+
+    /**
+     * Constructs a {@link GetCandidateCredentialsException}.
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public GetCandidateCredentialsException(@NonNull String type) {
+        this(type, null, null);
+    }
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.aidl b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl
new file mode 100644
index 0000000..d361089
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+parcelable GetCandidateCredentialsRequest;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.java b/core/java/android/credentials/GetCandidateCredentialsRequest.java
new file mode 100644
index 0000000..7f0dcaf
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsRequest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A request to retrieve a list of candidate credentials against the list of credential
+ * options
+ *
+ * @hide
+ */
+@Hide
+public final class GetCandidateCredentialsRequest implements Parcelable {
+
+    /**
+     * The list of credential requests.
+     */
+    @NonNull
+    private final List<CredentialOption> mCredentialOptions;
+
+    /**
+     * The top request level data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
+     * The origin of the calling app. Callers of this special API (e.g. browsers)
+     * can set this origin for an app different from their own, to be able to get credentials
+     * on behalf of that app.
+     */
+    @Nullable
+    private String mOrigin;
+
+    /**
+     * Returns the list of credential options to be requested.
+     */
+    @NonNull
+    public List<CredentialOption> getCredentialOptions() {
+        return mCredentialOptions;
+    }
+
+    /**
+     * Returns the top request level data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
+    /**
+     * Returns the origin of the calling app if set otherwise returns null.
+     */
+    @Nullable
+    public String getOrigin() {
+        return mOrigin;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedList(mCredentialOptions, flags);
+        dest.writeBundle(mData);
+        dest.writeString8(mOrigin);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCandidateCredentialsRequest {credentialOption=" + mCredentialOptions
+                + ", data=" + mData
+                + ", origin=" + mOrigin
+                + "}";
+    }
+
+    private GetCandidateCredentialsRequest(@NonNull List<CredentialOption> credentialOptions,
+            @NonNull Bundle data, String origin) {
+        Preconditions.checkCollectionNotEmpty(
+                credentialOptions,
+                /*valueName=*/ "credentialOptions");
+        Preconditions.checkCollectionElementsNotNull(
+                credentialOptions,
+                /*valueName=*/ "credentialOptions");
+        mCredentialOptions = credentialOptions;
+        mData = requireNonNull(data,
+                "data must not be null");
+        mOrigin = origin;
+    }
+
+    private GetCandidateCredentialsRequest(@NonNull Parcel in) {
+        List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>();
+        in.readTypedList(credentialOptions, CredentialOption.CREATOR);
+        mCredentialOptions = credentialOptions;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialOptions);
+
+        Bundle data = in.readBundle();
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
+
+        mOrigin = in.readString8();
+    }
+
+    @NonNull
+    public static final Creator<GetCandidateCredentialsRequest> CREATOR =
+            new Creator<>() {
+                @Override
+                public GetCandidateCredentialsRequest[] newArray(int size) {
+                    return new GetCandidateCredentialsRequest[size];
+                }
+
+                @Override
+                public GetCandidateCredentialsRequest createFromParcel(@NonNull Parcel in) {
+                    return new GetCandidateCredentialsRequest(in);
+                }
+            };
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.aidl b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl
new file mode 100644
index 0000000..ffcd3e7
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+parcelable GetCandidateCredentialsResponse;
\ No newline at end of file
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
new file mode 100644
index 0000000..1d649eb
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+import android.annotation.Hide;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list of candidate credentials.
+ *
+ * @hide
+ */
+@Hide
+public final class GetCandidateCredentialsResponse implements Parcelable {
+    // TODO(b/299321990): Add members
+    protected GetCandidateCredentialsResponse(Parcel in) {
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<GetCandidateCredentialsResponse> CREATOR =
+            new Creator<GetCandidateCredentialsResponse>() {
+                @Override
+                public GetCandidateCredentialsResponse createFromParcel(Parcel in) {
+                    return new GetCandidateCredentialsResponse(in);
+                }
+
+                @Override
+                public GetCandidateCredentialsResponse[] newArray(int size) {
+                    return new GetCandidateCredentialsResponse[size];
+                }
+            };
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index dec729f..42323d6 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -21,12 +21,14 @@
 import android.credentials.CredentialProviderInfo;
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCandidateCredentialsRequest;
 import android.credentials.GetCredentialRequest;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetCandidateCredentialsCallback;
 import android.credentials.IPrepareGetCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
 import android.content.ComponentName;
@@ -45,6 +47,8 @@
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
+    @nullable ICancellationSignal getCandidateCredentials(in GetCandidateCredentialsRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);
+
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
     void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl
new file mode 100644
index 0000000..729176a
--- /dev/null
+++ b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package android.credentials;
+
+import android.app.PendingIntent;
+import android.credentials.GetCandidateCredentialsResponse;
+
+/**
+ * Listener for a getCandidateCredentials request.
+ *
+ * @hide
+ */
+interface IGetCandidateCredentialsCallback {
+    oneway void onResponse(in GetCandidateCredentialsResponse response);
+    oneway void onError(String errorType, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 4700720..b1aa7de 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -32,7 +32,6 @@
 import android.view.SurfaceControl.RefreshRateRange;
 import android.view.SurfaceControl.Transaction;
 import android.window.DisplayWindowPolicyController;
-import android.window.ScreenCapture;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -112,25 +111,6 @@
     public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener);
 
     /**
-     * Screenshot for internal system-only use such as rotation, etc.  This method includes
-     * secure layers and the result should never be exposed to non-system applications.
-     * This method does not apply any rotation and provides the output in natural orientation.
-     *
-     * @param displayId The display id to take the screenshot of.
-     * @return The buffer or null if we have failed.
-     */
-    public abstract ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId);
-
-    /**
-     * General screenshot functionality that excludes secure layers and applies appropriate
-     * rotation that the device is currently in.
-     *
-     * @param displayId The display id to take the screenshot of.
-     * @return The buffer or null if we have failed.
-     */
-    public abstract ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId);
-
-    /**
      * Returns information about the specified logical display.
      *
      * @param displayId The logical display id.
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c3fae55..88d7231 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -41,6 +41,7 @@
 import android.view.InputEvent;
 import android.view.InputMonitor;
 import android.view.PointerIcon;
+import android.view.KeyCharacterMap;
 import android.view.VerifiedInputEvent;
 
 /** @hide */
@@ -63,6 +64,8 @@
     // active keyboard layout.
     int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode);
 
+    KeyCharacterMap getKeyCharacterMap(String layoutDescriptor);
+
     // Temporarily changes the pointer speed.
     void tryPointerSpeed(int speed);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a0cceae..ff1a6ac 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,8 @@
 
 package android.hardware.input;
 
+import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
+
 import android.Manifest;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -31,6 +33,7 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.graphics.drawable.Drawable;
 import android.hardware.BatteryState;
 import android.os.Build;
 import android.os.Handler;
@@ -931,6 +934,31 @@
     }
 
     /**
+     * Provides a Keyboard layout preview of a particular dimension.
+     *
+     * @param keyboardLayout Layout whose preview is requested. If null, will return preview of
+     *                       the default Keyboard layout defined by {@code Generic.kl}.
+     * @param width Expected width of the drawable
+     * @param height Expected height of the drawable
+     *
+     * NOTE: Width and height will auto-adjust to the width and height of the ImageView that
+     * shows the drawable but this allows the caller to provide an intrinsic width and height of
+     * the drawable allowing the ImageView to properly wrap the drawable content.
+     *
+     * @hide
+     */
+    @Nullable
+    public Drawable getKeyboardLayoutPreview(@Nullable KeyboardLayout keyboardLayout, int width,
+            int height) {
+        if (!keyboardLayoutPreviewFlag()) {
+            return null;
+        }
+        PhysicalKeyLayout keyLayout = new PhysicalKeyLayout(
+                mGlobal.getKeyCharacterMap(keyboardLayout), keyboardLayout);
+        return new KeyboardLayoutPreviewDrawable(mContext, keyLayout, width, height);
+    }
+
+    /**
      * Injects an input event into the event system, targeting windows owned by the provided uid.
      *
      * If a valid targetUid is provided, the system will only consider injecting the input event
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index e886f68..8c598ae 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -51,6 +51,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
 import android.view.PointerIcon;
 
 import com.android.internal.annotations.GuardedBy;
@@ -1206,6 +1207,21 @@
     }
 
     /**
+     * Returns KeyCharacterMap for the provided Keyboard layout. If provided layout is null it will
+     * return KeyCharacter map for the default layout {@code Generic.kl}.
+     */
+    public KeyCharacterMap getKeyCharacterMap(@Nullable KeyboardLayout keyboardLayout) {
+        if (keyboardLayout == null) {
+            return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+        }
+        try {
+            return mIm.getKeyCharacterMap(keyboardLayout.getDescriptor());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * @see InputManager#injectInputEvent(InputEvent, int, int)
      */
 
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 4403251..bbfed24 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -22,6 +22,7 @@
 import android.os.Parcelable;
 
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
@@ -230,6 +231,33 @@
         return mProductId;
     }
 
+    /**
+     * Returns if the Keyboard layout follows the ANSI Physical key layout.
+     */
+    public boolean isAnsiLayout() {
+        for (int i = 0; i < mLocales.size(); i++) {
+            Locale locale = mLocales.get(i);
+            if (locale != null && locale.getCountry().equalsIgnoreCase("us")
+                    && mLayoutType != LayoutType.EXTENDED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns if the Keyboard layout follows the JIS Physical key layout.
+     */
+    public boolean isJisLayout() {
+        for (int i = 0; i < mLocales.size(); i++) {
+            Locale locale = mLocales.get(i);
+            if (locale != null && locale.getCountry().equalsIgnoreCase("jp")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
new file mode 100644
index 0000000..d943c37
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2023 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.hardware.input;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom drawable class that draws preview of a Physical keyboard layout.
+ */
+final class KeyboardLayoutPreviewDrawable extends Drawable {
+
+    private static final String TAG = "KeyboardLayoutPreview";
+    private static final int GRAVITY_LEFT = 0x1;
+    private static final int GRAVITY_RIGHT = 0x2;
+    private static final int GRAVITY_TOP = 0x4;
+    private static final int GRAVITY_BOTTOM = 0x8;
+    private static final int GRAVITY_CENTER =
+            GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM;
+    private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT;
+    private static final int KEY_PADDING_IN_DP = 3;
+    private static final int KEYBOARD_PADDING_IN_DP = 10;
+    private static final int KEY_RADIUS_IN_DP = 5;
+    private static final int KEYBOARD_RADIUS_IN_DP = 10;
+    private static final int GLYPH_TEXT_SIZE_IN_SP = 10;
+
+    private final List<KeyDrawable> mKeyDrawables = new ArrayList<>();
+
+    private final int mWidth;
+    private final int mHeight;
+    private final RectF mKeyboardBackground = new RectF();
+    private final ResourceProvider mResourceProvider;
+    private final PhysicalKeyLayout mKeyLayout;
+
+    public KeyboardLayoutPreviewDrawable(Context context, PhysicalKeyLayout keyLayout, int width,
+            int height) {
+        mWidth = width;
+        mHeight = height;
+        mResourceProvider = new ResourceProvider(context);
+        mKeyLayout = keyLayout;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mHeight;
+    }
+
+    @Override
+    protected void onBoundsChange(@NonNull Rect bounds) {
+        super.onBoundsChange(bounds);
+        mKeyDrawables.clear();
+        final PhysicalKeyLayout.LayoutKey[][] keys = mKeyLayout.getKeys();
+        if (keys == null) {
+            return;
+        }
+        final PhysicalKeyLayout.EnterKey enterKey = mKeyLayout.getEnterKey();
+        int width = bounds.width();
+        int height = bounds.height();
+        final int keyboardPadding = mResourceProvider.getKeyboardPadding();
+        final int keyPadding = mResourceProvider.getKeyPadding();
+        final float keyRadius = mResourceProvider.getKeyRadius();
+        mKeyboardBackground.set(0, 0, width, height);
+        width -= keyboardPadding * 2;
+        height -= keyboardPadding * 2;
+        if (width <= 0 || height <= 0) {
+            Slog.e(TAG, "Invalid width and height to draw layout preview, width = " + width
+                    + ", height = " + height);
+            return;
+        }
+        int rowCount = keys.length;
+        float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount;
+        float isoEnterKeyLeft = 0;
+        float isoEnterKeyTop = 0;
+        float isoEnterWidthUnit = 0;
+        for (int i = 0; i < rowCount; i++) {
+            PhysicalKeyLayout.LayoutKey[] row = keys[i];
+            float totalRowWeight = 0;
+            int keysInRow = row.length;
+            for (PhysicalKeyLayout.LayoutKey layoutKey : row) {
+                totalRowWeight += layoutKey.keyWeight();
+            }
+            float keyWidthInPx = (width - keysInRow * 2 * keyPadding) / totalRowWeight;
+            float rowWeightOnLeft = 0;
+            float top = keyboardPadding + keyPadding * (2 * i + 1) + i * keyHeight;
+            for (int j = 0; j < keysInRow; j++) {
+                float left =
+                        keyboardPadding + keyPadding * (2 * j + 1) + rowWeightOnLeft * keyWidthInPx;
+                rowWeightOnLeft += row[j].keyWeight();
+                RectF keyRect = new RectF(left, top, left + keyWidthInPx * row[j].keyWeight(),
+                        top + keyHeight);
+                if (enterKey != null && row[j].keyCode() == KeyEvent.KEYCODE_ENTER) {
+                    if (enterKey.row() == i && enterKey.column() == j) {
+                        isoEnterKeyLeft = keyRect.left;
+                        isoEnterKeyTop = keyRect.top;
+                        isoEnterWidthUnit = keyWidthInPx;
+                    }
+                    continue;
+                }
+                if (PhysicalKeyLayout.isSpecialKey(row[j])) {
+                    mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius,
+                            mResourceProvider.getSpecialKeyPaint(),
+                            mResourceProvider.getSpecialKeyPaint(),
+                            mResourceProvider.getSpecialKeyPaint()));
+                } else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) {
+                    mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect,
+                            keyRadius, mResourceProvider.getTypingKeyPaint(),
+                            mResourceProvider.getPrimaryGlyphPaint(),
+                            mResourceProvider.getSecondaryGlyphPaint()));
+                } else {
+                    mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius,
+                            mResourceProvider.getTypingKeyPaint(),
+                            mResourceProvider.getPrimaryGlyphPaint(),
+                            mResourceProvider.getSecondaryGlyphPaint()));
+                }
+            }
+        }
+        if (enterKey != null) {
+            IsoEnterKey.Builder isoEnterKeyBuilder = new IsoEnterKey.Builder(keyRadius,
+                    mResourceProvider.getSpecialKeyPaint());
+            isoEnterKeyBuilder.setTopWidth(enterKey.topKeyWeight() * isoEnterWidthUnit)
+                    .setStartPoint(isoEnterKeyLeft, isoEnterKeyTop)
+                    .setVerticalEdges(keyHeight, 2 * (keyHeight + keyPadding))
+                    .setBottomWidth(enterKey.bottomKeyWeight() * isoEnterWidthUnit);
+            mKeyDrawables.add(isoEnterKeyBuilder.build());
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        final float keyboardRadius = mResourceProvider.getBackgroundRadius();
+        canvas.drawRoundRect(mKeyboardBackground, keyboardRadius, keyboardRadius,
+                mResourceProvider.getBackgroundPaint());
+        for (KeyDrawable key : mKeyDrawables) {
+            key.draw(canvas);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        // Do nothing
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        // Do nothing
+    }
+
+    @Override
+    public int getOpacity() {
+        return PixelFormat.OPAQUE;
+    }
+
+    private static class TypingKey implements KeyDrawable {
+
+        private final RectF mKeyRect;
+        private final float mKeyRadius;
+        private final Paint mKeyPaint;
+        private final Paint mBaseTextPaint;
+        private final Paint mModifierTextPaint;
+        private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>();
+
+        private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect,
+                float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) {
+            mKeyRect = keyRect;
+            mKeyRadius = keyRadius;
+            mKeyPaint = keyPaint;
+            mBaseTextPaint = baseTextPaint;
+            mModifierTextPaint = modifierTextPaint;
+            initGlyphs(glyphData);
+        }
+
+        private void initGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) {
+            createGlyphs(glyphData);
+            measureGlyphs();
+        }
+
+        private void createGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) {
+            if (glyphData == null) {
+                return;
+            }
+            if (!glyphData.hasBaseText()) {
+                return;
+            }
+            if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
+                        GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
+                        GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
+            } else if (glyphData.hasValidShiftText()) {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                        GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
+                        GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
+            } else if (glyphData.hasValidAltGrText()) {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                        GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
+                        GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
+            } else {
+                mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+                        GRAVITY_CENTER, mBaseTextPaint));
+            }
+        }
+
+        private void measureGlyphs() {
+            float keyWidth = mKeyRect.width();
+            float keyHeight = mKeyRect.height();
+            for (GlyphDrawable glyph : mGlyphDrawables) {
+                float centerX = keyWidth / 2;
+                float centerY = keyHeight / 2;
+                if ((glyph.gravity & GRAVITY_LEFT) != 0) {
+                    centerX -= keyWidth / 4;
+                }
+                if ((glyph.gravity & GRAVITY_RIGHT) != 0) {
+                    centerX += keyWidth / 4;
+                }
+                if ((glyph.gravity & GRAVITY_TOP) != 0) {
+                    centerY -= keyHeight / 4;
+                }
+                if ((glyph.gravity & GRAVITY_BOTTOM) != 0) {
+                    centerY += keyHeight / 4;
+                }
+                Rect textBounds = new Rect();
+                glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds);
+                float textWidth = textBounds.width();
+                float textHeight = textBounds.height();
+                glyph.rect.set(centerX - textWidth / 2, centerY - textHeight / 2 - textBounds.top,
+                        centerX + textWidth / 2, centerY + textHeight / 2 - textBounds.top);
+            }
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            canvas.drawRoundRect(mKeyRect, mKeyRadius, mKeyRadius, mKeyPaint);
+            for (GlyphDrawable glyph : mGlyphDrawables) {
+                float textWidth = glyph.rect.width();
+                float textHeight = glyph.rect.height();
+                float keyWidth = mKeyRect.width();
+                float keyHeight = mKeyRect.height();
+                if (textWidth == 0 || textHeight == 0 || keyWidth == 0 || keyHeight == 0) {
+                    return;
+                }
+                canvas.drawText(glyph.text, 0, glyph.text.length(), mKeyRect.left + glyph.rect.left,
+                        mKeyRect.top + glyph.rect.top, glyph.paint);
+            }
+        }
+    }
+
+    private static class UnsureTypingKey extends TypingKey {
+
+        private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData,
+                RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint,
+                Paint modifierTextPaint) {
+            super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint),
+                    createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint));
+        }
+    }
+
+    private static class IsoEnterKey implements KeyDrawable {
+
+        private final Paint mKeyPaint;
+        private final Path mPath;
+
+        private IsoEnterKey(Paint keyPaint, @NonNull Path path) {
+            mKeyPaint = keyPaint;
+            mPath = path;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            canvas.drawPath(mPath, mKeyPaint);
+        }
+
+        private static class Builder {
+            private final float mKeyRadius;
+            private final Paint mKeyPaint;
+            private float mLeft;
+            private float mTop;
+            private float mTopWidth;
+            private float mBottomWidth;
+            private float mLeftHeight;
+            private float mRightHeight;
+
+            private Builder(float keyRadius, Paint keyPaint) {
+                mKeyRadius = keyRadius;
+                mKeyPaint = keyPaint;
+            }
+
+            private Builder setStartPoint(float left, float top) {
+                mLeft = left;
+                mTop = top;
+                return this;
+            }
+
+            private Builder setTopWidth(float width) {
+                mTopWidth = width;
+                return this;
+            }
+
+            private Builder setBottomWidth(float width) {
+                mBottomWidth = width;
+                return this;
+            }
+
+            private Builder setVerticalEdges(float leftHeight, float rightHeight) {
+                mLeftHeight = leftHeight;
+                mRightHeight = rightHeight;
+                return this;
+            }
+
+            private IsoEnterKey build() {
+                Path enterKey = new Path();
+                RectF oval = new RectF(-mKeyRadius, -mKeyRadius, mKeyRadius, mKeyRadius);
+                // Horizontal top line
+                enterKey.moveTo(mLeft + mKeyRadius, mTop);
+                enterKey.lineTo(mLeft + mTopWidth - mKeyRadius, mTop);
+                // Rounded top right corner
+                oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius, mTop);
+                enterKey.arcTo(oval, 270, 90);
+                // Vertical right line
+                enterKey.lineTo(mLeft + mTopWidth, mTop + mRightHeight - mKeyRadius);
+                // Rounded bottom right corner
+                oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius,
+                        mTop + mRightHeight - 2 * mKeyRadius);
+                enterKey.arcTo(oval, 0, 90);
+                // Horizontal bottom line
+                enterKey.lineTo(mLeft + mTopWidth - mBottomWidth + mKeyRadius, mTop + mRightHeight);
+                // Rounded bottom left corner
+                oval.offsetTo(mLeft + mTopWidth - mBottomWidth,
+                        mTop + mRightHeight - 2 * mKeyRadius);
+                enterKey.arcTo(oval, 90, 90);
+                // Vertical left line (bottom half)
+                enterKey.lineTo(mLeft + mTopWidth - mBottomWidth, mTop + mLeftHeight - mKeyRadius);
+                // Rounded corner
+                oval.offsetTo(mLeft + mTopWidth - mBottomWidth - 2 * mKeyRadius,
+                        mTop + mLeftHeight);
+                enterKey.arcTo(oval, 0, -90);
+                // Horizontal line in the mid part
+                enterKey.lineTo(mLeft + mKeyRadius, mTop + mLeftHeight);
+                // Rounded corner
+                oval.offsetTo(mLeft, mTop + mLeftHeight - 2 * mKeyRadius);
+                enterKey.arcTo(oval, 90, 90);
+                // Vertical left line (top half)
+                enterKey.lineTo(mLeft, mTop + mKeyRadius);
+                // Rounded top left corner
+                oval.offsetTo(mLeft, mTop);
+                enterKey.arcTo(oval, 180, 90);
+                enterKey.close();
+                return new IsoEnterKey(mKeyPaint, enterKey);
+            }
+        }
+    }
+
+    private record GlyphDrawable(String text, RectF rect, int gravity, Paint paint) {}
+
+    private interface KeyDrawable {
+        void draw(Canvas canvas);
+    }
+
+    private static class ResourceProvider {
+        // Resources
+        private final Paint mBackgroundPaint;
+        private final Paint mTypingKeyPaint;
+        private final Paint mSpecialKeyPaint;
+        private final Paint mPrimaryGlyphPaint;
+        private final Paint mSecondaryGlyphPaint;
+        private final int mKeyPadding;
+        private final int mKeyboardPadding;
+        private final float mKeyRadius;
+        private final float mBackgroundRadius;
+
+        private ResourceProvider(Context context) {
+            mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    KEY_PADDING_IN_DP, context.getResources().getDisplayMetrics());
+            mKeyboardPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    KEYBOARD_PADDING_IN_DP, context.getResources().getDisplayMetrics());
+            mKeyRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
+            mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                    KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
+            int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                    GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics());
+            boolean isDark = (context.getResources().getConfiguration().uiMode
+                    & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+            int typingKeyColor = context.getColor(
+                    isDark ? android.R.color.system_outline_variant_dark
+                            : android.R.color.system_surface_container_lowest_light);
+            int specialKeyColor = context.getColor(isDark ? android.R.color.system_neutral1_800
+                    : android.R.color.system_secondary_container_light);
+            int primaryGlyphColor = context.getColor(isDark ? android.R.color.system_on_surface_dark
+                    : android.R.color.system_on_surface_light);
+            int secondaryGlyphColor = context.getColor(isDark ? android.R.color.system_outline_dark
+                    : android.R.color.system_outline_light);
+            int backgroundColor = context.getColor(
+                    isDark ? android.R.color.system_surface_container_dark
+                            : android.R.color.system_surface_container_light);
+            mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize,
+                    Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
+            mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize,
+                    Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+            mTypingKeyPaint = createFillPaint(typingKeyColor);
+            mSpecialKeyPaint = createFillPaint(specialKeyColor);
+            mBackgroundPaint = createFillPaint(backgroundColor);
+        }
+
+        private Paint getBackgroundPaint() {
+            return mBackgroundPaint;
+        }
+
+        private Paint getTypingKeyPaint() {
+            return mTypingKeyPaint;
+        }
+
+        private Paint getSpecialKeyPaint() {
+            return mSpecialKeyPaint;
+        }
+
+        private Paint getPrimaryGlyphPaint() {
+            return mPrimaryGlyphPaint;
+        }
+
+        private Paint getSecondaryGlyphPaint() {
+            return mSecondaryGlyphPaint;
+        }
+
+        private int getKeyPadding() {
+            return mKeyPadding;
+        }
+
+        private int getKeyboardPadding() {
+            return mKeyboardPadding;
+        }
+
+        private float getKeyRadius() {
+            return mKeyRadius;
+        }
+
+        private float getBackgroundRadius() {
+            return mBackgroundRadius;
+        }
+    }
+
+    private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) {
+        Paint paint = new Paint();
+        paint.setColor(textColor);
+        paint.setStyle(Paint.Style.FILL);
+        paint.setTextSize(textSize);
+        paint.setTypeface(typeface);
+        return paint;
+    }
+
+    private static Paint createFillPaint(@ColorInt int color) {
+        Paint paint = new Paint();
+        paint.setColor(color);
+        paint.setStyle(Paint.Style.FILL);
+        return paint;
+    }
+
+    private static Paint createGreyedOutPaint(Paint paint) {
+        Paint result = new Paint(paint);
+        result.setAlpha(100);
+        return result;
+    }
+}
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
new file mode 100644
index 0000000..241c452
--- /dev/null
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2023 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.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import java.util.Locale;
+
+/**
+ * A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout
+ * of a Physical keyboard and provides information regarding the scan codes produced by the physical
+ * keys.
+ */
+final class PhysicalKeyLayout {
+
+    private static final String TAG = "KeyboardLayoutPreview";
+    private static final int SCANCODE_1 = 2;
+    private static final int SCANCODE_2 = 3;
+    private static final int SCANCODE_3 = 4;
+    private static final int SCANCODE_4 = 5;
+    private static final int SCANCODE_5 = 6;
+    private static final int SCANCODE_6 = 7;
+    private static final int SCANCODE_7 = 8;
+    private static final int SCANCODE_8 = 9;
+    private static final int SCANCODE_9 = 10;
+    private static final int SCANCODE_0 = 11;
+    private static final int SCANCODE_MINUS = 12;
+    private static final int SCANCODE_EQUALS = 13;
+    private static final int SCANCODE_Q = 16;
+    private static final int SCANCODE_W = 17;
+    private static final int SCANCODE_E = 18;
+    private static final int SCANCODE_R = 19;
+    private static final int SCANCODE_T = 20;
+    private static final int SCANCODE_Y = 21;
+    private static final int SCANCODE_U = 22;
+    private static final int SCANCODE_I = 23;
+    private static final int SCANCODE_O = 24;
+    private static final int SCANCODE_P = 25;
+    private static final int SCANCODE_LEFT_BRACKET = 26;
+    private static final int SCANCODE_RIGHT_BRACKET = 27;
+    private static final int SCANCODE_A = 30;
+    private static final int SCANCODE_S = 31;
+    private static final int SCANCODE_D = 32;
+    private static final int SCANCODE_F = 33;
+    private static final int SCANCODE_G = 34;
+    private static final int SCANCODE_H = 35;
+    private static final int SCANCODE_J = 36;
+    private static final int SCANCODE_K = 37;
+    private static final int SCANCODE_L = 38;
+    private static final int SCANCODE_SEMICOLON = 39;
+    private static final int SCANCODE_APOSTROPHE = 40;
+    private static final int SCANCODE_GRAVE = 41;
+    private static final int SCANCODE_BACKSLASH1 = 43;
+    private static final int SCANCODE_Z = 44;
+    private static final int SCANCODE_X = 45;
+    private static final int SCANCODE_C = 46;
+    private static final int SCANCODE_V = 47;
+    private static final int SCANCODE_B = 48;
+    private static final int SCANCODE_N = 49;
+    private static final int SCANCODE_M = 50;
+    private static final int SCANCODE_COMMA = 51;
+    private static final int SCANCODE_PERIOD = 52;
+    private static final int SCANCODE_SLASH = 53;
+    private static final int SCANCODE_BACKSLASH2 = 86;
+    private static final int SCANCODE_YEN = 124;
+
+    private static final SparseIntArray DEFAULT_KEYCODE_FOR_SCANCODE = new SparseIntArray();
+
+    static {
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_1, KeyEvent.KEYCODE_1);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_2, KeyEvent.KEYCODE_2);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_3, KeyEvent.KEYCODE_3);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_4, KeyEvent.KEYCODE_4);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_5, KeyEvent.KEYCODE_5);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_6, KeyEvent.KEYCODE_6);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_7, KeyEvent.KEYCODE_7);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_8, KeyEvent.KEYCODE_8);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_9, KeyEvent.KEYCODE_9);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_0, KeyEvent.KEYCODE_0);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_MINUS, KeyEvent.KEYCODE_MINUS);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_EQUALS, KeyEvent.KEYCODE_EQUALS);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Q, KeyEvent.KEYCODE_Q);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_W, KeyEvent.KEYCODE_W);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_E, KeyEvent.KEYCODE_E);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_R, KeyEvent.KEYCODE_R);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_T, KeyEvent.KEYCODE_T);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Y, KeyEvent.KEYCODE_Y);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_U, KeyEvent.KEYCODE_U);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_I, KeyEvent.KEYCODE_I);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_O, KeyEvent.KEYCODE_O);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_P, KeyEvent.KEYCODE_P);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_LEFT_BRACKET, KeyEvent.KEYCODE_LEFT_BRACKET);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_RIGHT_BRACKET);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_A, KeyEvent.KEYCODE_A);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_S, KeyEvent.KEYCODE_S);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_D, KeyEvent.KEYCODE_D);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_F, KeyEvent.KEYCODE_F);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_G, KeyEvent.KEYCODE_G);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_H, KeyEvent.KEYCODE_H);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_J, KeyEvent.KEYCODE_J);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_K, KeyEvent.KEYCODE_K);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_L, KeyEvent.KEYCODE_L);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SEMICOLON, KeyEvent.KEYCODE_SEMICOLON);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_APOSTROPHE, KeyEvent.KEYCODE_APOSTROPHE);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_GRAVE, KeyEvent.KEYCODE_GRAVE);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH1, KeyEvent.KEYCODE_BACKSLASH);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Z, KeyEvent.KEYCODE_Z);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_X, KeyEvent.KEYCODE_X);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_C, KeyEvent.KEYCODE_C);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_V, KeyEvent.KEYCODE_V);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_B, KeyEvent.KEYCODE_B);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_N, KeyEvent.KEYCODE_N);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_M, KeyEvent.KEYCODE_M);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_COMMA, KeyEvent.KEYCODE_COMMA);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_PERIOD, KeyEvent.KEYCODE_PERIOD);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SLASH, KeyEvent.KEYCODE_SLASH);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH2, KeyEvent.KEYCODE_BACKSLASH);
+        DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_YEN, KeyEvent.KEYCODE_YEN);
+    }
+
+    private LayoutKey[][] mKeys = null;
+    private EnterKey mEnterKey = null;
+
+    public PhysicalKeyLayout(@NonNull KeyCharacterMap kcm, @Nullable KeyboardLayout layout) {
+        initLayoutKeys(kcm, layout);
+    }
+
+    private void initLayoutKeys(KeyCharacterMap kcm, KeyboardLayout layout) {
+        if (layout == null) {
+            createIsoLayout(kcm);
+            return;
+        }
+        if (layout.isAnsiLayout()) {
+            createAnsiLayout(kcm);
+        } else if (layout.isJisLayout()) {
+            createJisLayout(kcm);
+        } else {
+            createIsoLayout(kcm);
+        }
+    }
+
+    public LayoutKey[][] getKeys() {
+        return mKeys;
+    }
+
+    /**
+     * @return Special enter key (if required) that can span multiple rows like ISO enter key.
+     */
+    @Nullable
+    public EnterKey getEnterKey() {
+        return mEnterKey;
+    }
+
+    private void createAnsiLayout(KeyCharacterMap kcm) {
+        mKeys = new LayoutKey[][]{
+                {
+                        getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+                        getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+                        getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+                        getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+                        getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS),
+                        getKey(KeyEvent.KEYCODE_DEL, 1.5F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_Q),
+                        getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+                        getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+                        getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+                        getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+                        getKey(kcm, SCANCODE_BACKSLASH1)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_CAPS_LOCK, 1.75F),
+                        getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D),
+                        getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H),
+                        getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+                        getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+                        getKey(KeyEvent.KEYCODE_ENTER, 1.75F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 2.5F),
+                        getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C),
+                        getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N),
+                        getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA),
+                        getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH),
+                        getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.5F),
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_SPACE, 6.5F),
+                        getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+                        getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+                }
+        };
+    }
+
+    private void createIsoLayout(KeyCharacterMap kcm) {
+        mKeys = new LayoutKey[][]{
+                {
+                        getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+                        getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+                        getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+                        getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+                        getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS),
+                        getKey(KeyEvent.KEYCODE_DEL, 1.5F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q),
+                        getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+                        getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+                        getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+                        getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+                        getKey(KeyEvent.KEYCODE_ENTER, 1.35F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A),
+                        getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F),
+                        getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J),
+                        getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+                        getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+                        getKey(kcm, SCANCODE_BACKSLASH1),
+                        getKey(KeyEvent.KEYCODE_ENTER, 1.0F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F),
+                        getKey(kcm, SCANCODE_BACKSLASH2), getKey(kcm, SCANCODE_Z),
+                        getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V),
+                        getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M),
+                        getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD),
+                        getKey(kcm, SCANCODE_SLASH),
+                        getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_SPACE, 6.5F),
+                        getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+                        getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+                }
+        };
+        mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F);
+    }
+
+    private void createJisLayout(KeyCharacterMap kcm) {
+        mKeys = new LayoutKey[][]{
+                {
+                        getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+                        getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+                        getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+                        getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+                        getKey(kcm, SCANCODE_MINUS, 0.8F), getKey(kcm, SCANCODE_EQUALS, 0.8f),
+                        getKey(kcm, SCANCODE_YEN, 0.8f), getKey(KeyEvent.KEYCODE_DEL, 1.1F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q),
+                        getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+                        getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+                        getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+                        getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+                        getKey(KeyEvent.KEYCODE_ENTER, 1.35F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A),
+                        getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F),
+                        getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J),
+                        getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+                        getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+                        getKey(kcm, SCANCODE_BACKSLASH2),
+                        getKey(KeyEvent.KEYCODE_ENTER, 1.0F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F),
+                        getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C),
+                        getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N),
+                        getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA),
+                        getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH),
+                        getKey(kcm, SCANCODE_BACKSLASH1),
+                        getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F)
+                },
+                {
+                        getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+                        getKey(KeyEvent.KEYCODE_SPACE, 3.5F),
+                        getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+                        getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+                        getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+                        getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+                        getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+                }
+        };
+        mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F);
+    }
+
+    private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode, float keyWeight) {
+        int keyCode = kcm.getMappedKeyOrDefault(scanCode,
+                DEFAULT_KEYCODE_FOR_SCANCODE.get(scanCode, KeyEvent.KEYCODE_UNKNOWN));
+        return new LayoutKey(keyCode, scanCode, keyWeight, new KeyGlyph(kcm, keyCode));
+    }
+
+    private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode) {
+        return getKey(kcm, scanCode, 1.0F);
+    }
+
+    private static String getKeyText(KeyCharacterMap kcm, int keyCode, int modifierState) {
+        if (isSpecialKey(keyCode)) {
+            return "";
+        }
+        int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
+        if (Character.isValidCodePoint(utf8Char)) {
+            return String.valueOf(Character.toChars(utf8Char)).toUpperCase(Locale.getDefault());
+        } else {
+            return String.valueOf(kcm.getDisplayLabel(keyCode)).toUpperCase(Locale.getDefault());
+        }
+    }
+
+    private static LayoutKey getKey(int keyCode, float keyWeight) {
+        return new LayoutKey(keyCode, keyCode, keyWeight, null);
+    }
+
+    /**
+     * Util function that tells if a key corresponds to a special key which are keys on a Physical
+     * layout that perform some special action like modifier keys, enter key, space key, character
+     * set changing keys, etc.
+     */
+    private static boolean isSpecialKey(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_DEL:
+            case KeyEvent.KEYCODE_TAB:
+            case KeyEvent.KEYCODE_CAPS_LOCK:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_SHIFT_LEFT:
+            case KeyEvent.KEYCODE_SHIFT_RIGHT:
+            case KeyEvent.KEYCODE_CTRL_LEFT:
+            case KeyEvent.KEYCODE_CTRL_RIGHT:
+            case KeyEvent.KEYCODE_FUNCTION:
+            case KeyEvent.KEYCODE_ALT_LEFT:
+            case KeyEvent.KEYCODE_ALT_RIGHT:
+            case KeyEvent.KEYCODE_META_LEFT:
+            case KeyEvent.KEYCODE_META_RIGHT:
+            case KeyEvent.KEYCODE_SPACE:
+            case KeyEvent.KEYCODE_MENU:
+            case KeyEvent.KEYCODE_UNKNOWN:
+                return true;
+        }
+        return false;
+    }
+
+    public static boolean isSpecialKey(LayoutKey key) {
+        return isSpecialKey(key.keyCode);
+    }
+
+    public static boolean isKeyPositionUnsure(LayoutKey key) {
+        switch (key.scanCode) {
+            case SCANCODE_GRAVE:
+            case SCANCODE_BACKSLASH1:
+            case SCANCODE_BACKSLASH2:
+                return true;
+        }
+        return false;
+    }
+
+    public record LayoutKey(int keyCode, int scanCode, float keyWeight, KeyGlyph glyph) {}
+    public record EnterKey(int row, int column, float topKeyWeight, float bottomKeyWeight) {}
+
+    public static class KeyGlyph {
+        private final String mBaseText;
+        private final String mShiftText;
+        private final String mAltGrText;
+
+        public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
+            mBaseText = getKeyText(kcm, keyCode, 0);
+            mShiftText = getKeyText(kcm, keyCode,
+                    KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
+            mAltGrText = getKeyText(kcm, keyCode,
+                    KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON);
+        }
+
+        public String getBaseText() {
+            return mBaseText;
+        }
+
+        public String getShiftText() {
+            return mShiftText;
+        }
+
+        public String getAltGrText() {
+            return mAltGrText;
+        }
+
+        public boolean hasBaseText() {
+            return !TextUtils.isEmpty(mBaseText);
+        }
+
+        public boolean hasValidShiftText() {
+            return !TextUtils.isEmpty(mShiftText) && !TextUtils.equals(mBaseText, mShiftText);
+        }
+
+        public boolean hasValidAltGrText() {
+            return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
+        }
+    }
+}
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 24d1c95..1b8d925 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -104,7 +104,11 @@
      */
     void hide(boolean remove) {
         if (getDecorView() != null) {
-            getDecorView().setVisibility(remove ? View.GONE : View.INVISIBLE);
+            if (remove) {
+                mWindowManager.removeViewImmediate(getDecorView());
+            } else {
+                getDecorView().setVisibility(View.INVISIBLE);
+            }
         }
     }
 
diff --git a/core/java/android/nfc/NfcAntennaInfo.java b/core/java/android/nfc/NfcAntennaInfo.java
index d54fcd2..b002ca2 100644
--- a/core/java/android/nfc/NfcAntennaInfo.java
+++ b/core/java/android/nfc/NfcAntennaInfo.java
@@ -85,8 +85,8 @@
         this.mDeviceHeight = in.readInt();
         this.mDeviceFoldable = in.readByte() != 0;
         this.mAvailableNfcAntennas = new ArrayList<>();
-        in.readParcelableList(this.mAvailableNfcAntennas,
-                AvailableNfcAntenna.class.getClassLoader());
+        in.readTypedList(this.mAvailableNfcAntennas,
+                AvailableNfcAntenna.CREATOR);
     }
 
     public static final @NonNull Parcelable.Creator<NfcAntennaInfo> CREATOR =
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index f40efc8..218d4bb 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -186,6 +186,8 @@
     /**
      * Get the binder transaction observer for this process.
      *
+     * TODO(b/299356196): only applies to Java code, not C++/Rust
+     *
      * @hide
      */
     public static void setObserver(@Nullable BinderInternal.Observer observer) {
@@ -202,6 +204,8 @@
      * that require a result must be sent as {@link IBinder#FLAG_ONEWAY} calls
      * which deliver results through a callback interface.
      *
+     * TODO(b/299355525): only applies to Java code, not C++/Rust
+     *
      * @hide
      */
     public static void setWarnOnBlocking(boolean warnOnBlocking) {
@@ -218,6 +222,8 @@
      * interfaces hosted by package that could be upgraded or replaced,
      * otherwise you risk system instability if that remote interface wedges.
      *
+     * TODO(b/299355525): only applies to Java code, not C++/Rust
+     *
      * @hide
      */
     public static IBinder allowBlocking(IBinder binder) {
@@ -1307,6 +1313,8 @@
             int callingUid) {
         // Make sure the observer won't change while processing a transaction.
         final BinderInternal.Observer observer = sObserver;
+
+        // TODO(b/299356196): observer should also observe transactions in native code
         final CallSession callSession =
                 observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
         // Theoretically, we should call transact, which will call onTransact,
@@ -1329,7 +1337,7 @@
 
         final boolean tracingEnabled = tagEnabled && transactionTraceName != null;
         try {
-            // TODO - this logic should not be in Java - it should be in native
+            // TODO(b/299356201) - this logic should not be in Java - it should be in native
             // code in libbinder so that it works for all binder users.
             final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
             if (heavyHitterWatcher != null && callingUid != -1) {
@@ -1340,9 +1348,9 @@
                 Trace.traceBegin(Trace.TRACE_TAG_AIDL, transactionTraceName);
             }
 
-            // TODO - this logic should not be in Java - it should be in native
-            // code in libbinder so that it works for all binder users. Further,
-            // this should not re-use flags.
+            // TODO(b/299353919) - this logic should not be in Java - it should be
+            // in native code in libbinder so that it works for all binder users.
+            // Further, this should not re-use flags.
             if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0 && callingUid != -1) {
                 AppOpsManager.startNotedAppOpsCollection(callingUid);
                 try {
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 62d9c69..c527cb5 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
@@ -1953,6 +1954,23 @@
      */
     public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack);
 
+    /**
+     * Retrieves the RSS memory used by the process as given by the status file.
+     */
+    @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION)
+    public static native long getRss();
+
+    /**
+     * Retrieves the RSS memory used by the process as given by the status file. Optionally supply a
+     * long array of up to 4 entries to retrieve the total memtrack reported size, memtrack
+     * graphics, memtrack gl, and memtrack other.
+     *
+     * @return The RSS memory usage, or 0 if retrieval failed (i.e. the PID is gone).
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION)
+    public static native long getRss(int pid, long[] outMemtrack);
+
     /** @hide */
     public static final int MEMINFO_TOTAL = 0;
     /** @hide */
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
new file mode 100644
index 0000000..77be5d4
--- /dev/null
+++ b/core/java/android/permission/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.permission.flags"
+
+flag {
+  name: "device_aware_permission_apis"
+  namespace: "permissions"
+  description: "enable device aware permission APIs"
+  bug: "274852670"
+}
diff --git a/core/java/android/security/TEST_MAPPING b/core/java/android/security/TEST_MAPPING
index 7e43381..5a679b1 100644
--- a/core/java/android/security/TEST_MAPPING
+++ b/core/java/android/security/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-    "postsubmit": [
+    "presubmit": [
         {
             "name": "CtsSecurityTestCases",
             "options": [
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index 00c30b1..83f9662 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -63,6 +63,10 @@
     private static final String TAG_AUTOFILL_SERVICE = "autofill-service";
     private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package";
 
+    private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+            new ComponentName("com.android.credentialmanager",
+                    "com.android.credentialmanager.autofill.CredentialAutofillService");
+
     private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
             throws PackageManager.NameNotFoundException {
         try {
@@ -307,6 +311,11 @@
         for (ResolveInfo resolveInfo : resolveInfos) {
             final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             try {
+                if (serviceInfo != null && isCredentialManagerAutofillService(
+                        serviceInfo.getComponentName())) {
+                    // Skip this service as it is for internal use only
+                    continue;
+                }
                 services.add(new AutofillServiceInfo(context, serviceInfo));
             } catch (SecurityException e) {
                 // Service does not declare the proper permission, ignore it.
@@ -316,6 +325,13 @@
         return services;
     }
 
+    private static boolean isCredentialManagerAutofillService(ComponentName componentName) {
+        if (componentName == null) {
+            return false;
+        }
+        return componentName.equals(CREDMAN_SERVICE_COMPONENT_NAME);
+    }
+
     @Override
     public String toString() {
         final StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index b48b7ec..3f41c56 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -46,6 +46,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
+import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.ArraySet;
 import android.util.Log;
@@ -131,6 +132,9 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
     static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L;
 
+    private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+            SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
     IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
         @Override
         public void ready() {
@@ -947,6 +951,10 @@
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
+        if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) {
+            throw new IllegalStateException("VisualQueryDetectionService is not enabled on this "
+                    + "system. Please set ro.hotword.visual_query_service_enabled to true.");
+        }
         if (mSystemService == null) {
             throw new IllegalStateException("Not available until onReady() is called");
         }
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 0ed275c..2906d86 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -187,14 +187,6 @@
     public static final String SETTINGS_FLASH_NOTIFICATIONS = "settings_flash_notifications";
 
     /**
-     * Flag to disable/enable showing udfps enroll view in settings. If it's disabled, udfps enroll
-     * view is shown in system ui.
-     * @hide
-     */
-    public static final String SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS =
-            "settings_show_udfps_enroll_in_settings";
-
-    /**
      * Flag to enable lock screen credentials transfer API in Android U.
      * @hide
      */
@@ -208,14 +200,6 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
-    // TODO(b/295516544): Remove this when trunk stable feature flag is available.
-    /**
-     * Flag to enable / disable the Private Space Settings. It's disabled by default.
-     * @hide
-     */
-    public static final String SETTINGS_PRIVATE_SPACE_SETTINGS =
-            "settings_private_space_settings";
-
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -258,13 +242,11 @@
         DEFAULT_FLAGS.put(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM, "false");
         DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
         DEFAULT_FLAGS.put(SETTINGS_FLASH_NOTIFICATIONS, "true");
-        DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
-        DEFAULT_FLAGS.put(SETTINGS_PRIVATE_SPACE_SETTINGS, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/Patterns.java b/core/java/android/util/Patterns.java
index ece069f..c4660c4 100644
--- a/core/java/android/util/Patterns.java
+++ b/core/java/android/util/Patterns.java
@@ -111,7 +111,7 @@
     /**
      *  Regular expression to match all IANA top-level domains.
      *
-     *  List accurate as of 2015/11/24.  List taken from:
+     *  List accurate as of 2023/09/11.  List taken from:
      *  http://data.iana.org/TLD/tlds-alpha-by-domain.txt
      *  This pattern is auto-generated by frameworks/ex/common/tools/make-iana-tld-pattern.py
      *
@@ -119,121 +119,167 @@
      */
     static final String IANA_TOP_LEVEL_DOMAINS =
         "(?:"
-        + "(?:aaa|aarp|abb|abbott|abogado|academy|accenture|accountant|accountants|aco|active"
-        + "|actor|ads|adult|aeg|aero|afl|agency|aig|airforce|airtel|allfinanz|alsace|amica|amsterdam"
-        + "|android|apartments|app|apple|aquarelle|aramco|archi|army|arpa|arte|asia|associates"
-        + "|attorney|auction|audio|auto|autos|axa|azure|a[cdefgilmoqrstuwxz])"
-        + "|(?:band|bank|bar|barcelona|barclaycard|barclays|bargains|bauhaus|bayern|bbc|bbva"
-        + "|bcn|beats|beer|bentley|berlin|best|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black"
-        + "|blackfriday|bloomberg|blue|bms|bmw|bnl|bnpparibas|boats|bom|bond|boo|boots|boutique"
-        + "|bradesco|bridgestone|broadway|broker|brother|brussels|budapest|build|builders|business"
-        + "|buzz|bzh|b[abdefghijmnorstvwyz])"
-        + "|(?:cab|cafe|cal|camera|camp|cancerresearch|canon|capetown|capital|car|caravan|cards"
-        + "|care|career|careers|cars|cartier|casa|cash|casino|cat|catering|cba|cbn|ceb|center|ceo"
-        + "|cern|cfa|cfd|chanel|channel|chat|cheap|chloe|christmas|chrome|church|cipriani|cisco"
-        + "|citic|city|cityeats|claims|cleaning|click|clinic|clothing|cloud|club|clubmed|coach"
-        + "|codes|coffee|college|cologne|com|commbank|community|company|computer|comsec|condos"
-        + "|construction|consulting|contractors|cooking|cool|coop|corsica|country|coupons|courses"
-        + "|credit|creditcard|creditunion|cricket|crown|crs|cruises|csc|cuisinella|cymru|cyou|c[acdfghiklmnoruvwxyz])"
-        + "|(?:dabur|dad|dance|date|dating|datsun|day|dclk|deals|degree|delivery|dell|delta"
-        + "|democrat|dental|dentist|desi|design|dev|diamonds|diet|digital|direct|directory|discount"
-        + "|dnp|docs|dog|doha|domains|doosan|download|drive|durban|dvag|d[ejkmoz])"
-        + "|(?:earth|eat|edu|education|email|emerck|energy|engineer|engineering|enterprises"
-        + "|epson|equipment|erni|esq|estate|eurovision|eus|events|everbank|exchange|expert|exposed"
-        + "|express|e[cegrstu])"
-        + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|fashion|feedback|ferrero|film"
-        + "|final|finance|financial|firmdale|fish|fishing|fit|fitness|flights|florist|flowers|flsmidth"
-        + "|fly|foo|football|forex|forsale|forum|foundation|frl|frogans|fund|furniture|futbol|fyi"
-        + "|f[ijkmor])"
-        + "|(?:gal|gallery|game|garden|gbiz|gdn|gea|gent|genting|ggee|gift|gifts|gives|giving"
-        + "|glass|gle|global|globo|gmail|gmo|gmx|gold|goldpoint|golf|goo|goog|google|gop|gov|grainger"
-        + "|graphics|gratis|green|gripe|group|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
-        + "|(?:hamburg|hangout|haus|healthcare|help|here|hermes|hiphop|hitachi|hiv|hockey|holdings"
-        + "|holiday|homedepot|homes|honda|horse|host|hosting|hoteles|hotmail|house|how|hsbc|hyundai"
-        + "|h[kmnrtu])"
-        + "|(?:ibm|icbc|ice|icu|ifm|iinet|immo|immobilien|industries|infiniti|info|ing|ink|institute"
-        + "|insure|int|international|investments|ipiranga|irish|ist|istanbul|itau|iwc|i[delmnoqrst])"
-        + "|(?:jaguar|java|jcb|jetzt|jewelry|jlc|jll|jobs|joburg|jprs|juegos|j[emop])"
-        + "|(?:kaufen|kddi|kia|kim|kinder|kitchen|kiwi|koeln|komatsu|krd|kred|kyoto|k[eghimnprwyz])"
-        + "|(?:lacaixa|lancaster|land|landrover|lasalle|lat|latrobe|law|lawyer|lds|lease|leclerc"
-        + "|legal|lexus|lgbt|liaison|lidl|life|lifestyle|lighting|limited|limo|linde|link|live"
-        + "|lixil|loan|loans|lol|london|lotte|lotto|love|ltd|ltda|lupin|luxe|luxury|l[abcikrstuvy])"
-        + "|(?:madrid|maif|maison|man|management|mango|market|marketing|markets|marriott|mba"
-        + "|media|meet|melbourne|meme|memorial|men|menu|meo|miami|microsoft|mil|mini|mma|mobi|moda"
-        + "|moe|moi|mom|monash|money|montblanc|mormon|mortgage|moscow|motorcycles|mov|movie|movistar"
-        + "|mtn|mtpc|mtr|museum|mutuelle|m[acdeghklmnopqrstuvwxyz])"
-        + "|(?:nadex|nagoya|name|navy|nec|net|netbank|network|neustar|new|news|nexus|ngo|nhk"
-        + "|nico|ninja|nissan|nokia|nra|nrw|ntt|nyc|n[acefgilopruz])"
-        + "|(?:obi|office|okinawa|omega|one|ong|onl|online|ooo|oracle|orange|org|organic|osaka"
-        + "|otsuka|ovh|om)"
-        + "|(?:page|panerai|paris|partners|parts|party|pet|pharmacy|philips|photo|photography"
-        + "|photos|physio|piaget|pics|pictet|pictures|ping|pink|pizza|place|play|playstation|plumbing"
-        + "|plus|pohl|poker|porn|post|praxi|press|pro|prod|productions|prof|properties|property"
-        + "|protection|pub|p[aefghklmnrstwy])"
-        + "|(?:qpon|quebec|qa)"
-        + "|(?:racing|realtor|realty|recipes|red|redstone|rehab|reise|reisen|reit|ren|rent|rentals"
-        + "|repair|report|republican|rest|restaurant|review|reviews|rich|ricoh|rio|rip|rocher|rocks"
-        + "|rodeo|rsvp|ruhr|run|rwe|ryukyu|r[eosuw])"
-        + "|(?:saarland|sakura|sale|samsung|sandvik|sandvikcoromant|sanofi|sap|sapo|sarl|saxo"
-        + "|sbs|sca|scb|schmidt|scholarships|school|schule|schwarz|science|scor|scot|seat|security"
-        + "|seek|sener|services|seven|sew|sex|sexy|shiksha|shoes|show|shriram|singles|site|ski"
-        + "|sky|skype|sncf|soccer|social|software|sohu|solar|solutions|sony|soy|space|spiegel|spreadbetting"
-        + "|srl|stada|starhub|statoil|stc|stcgroup|stockholm|studio|study|style|sucks|supplies"
-        + "|supply|support|surf|surgery|suzuki|swatch|swiss|sydney|systems|s[abcdeghijklmnortuvxyz])"
-        + "|(?:tab|taipei|tatamotors|tatar|tattoo|tax|taxi|team|tech|technology|tel|telefonica"
-        + "|temasek|tennis|thd|theater|theatre|tickets|tienda|tips|tires|tirol|today|tokyo|tools"
-        + "|top|toray|toshiba|tours|town|toyota|toys|trade|trading|training|travel|trust|tui|t[cdfghjklmnortvwz])"
-        + "|(?:ubs|university|uno|uol|u[agksyz])"
-        + "|(?:vacations|vana|vegas|ventures|versicherung|vet|viajes|video|villas|vin|virgin"
-        + "|vision|vista|vistaprint|viva|vlaanderen|vodka|vote|voting|voto|voyage|v[aceginu])"
-        + "|(?:wales|walter|wang|watch|webcam|website|wed|wedding|weir|whoswho|wien|wiki|williamhill"
-        + "|win|windows|wine|wme|work|works|world|wtc|wtf|w[fs])"
-        + "|(?:\u03b5\u03bb|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438|\u043a\u043e\u043c|\u043c\u043a\u0434"
+        + "(?:aaa|aarp|abb|abbott|abbvie|abc|able|abogado|abudhabi|academy|accenture|accountant"
+        + "|accountants|aco|actor|ads|adult|aeg|aero|aetna|afl|africa|agakhan|agency|aig|airbus"
+        + "|airforce|airtel|akdn|alibaba|alipay|allfinanz|allstate|ally|alsace|alstom|amazon|americanexpress"
+        + "|americanfamily|amex|amfam|amica|amsterdam|analytics|android|anquan|anz|aol|apartments"
+        + "|app|apple|aquarelle|arab|aramco|archi|army|arpa|art|arte|asda|asia|associates|athleta"
+        + "|attorney|auction|audi|audible|audio|auspost|author|auto|autos|avianca|aws|axa|azure"
+        + "|a[cdefgilmoqrstuwxz])"
+        + "|(?:baby|baidu|banamex|bananarepublic|band|bank|bar|barcelona|barclaycard|barclays"
+        + "|barefoot|bargains|baseball|basketball|bauhaus|bayern|bbc|bbt|bbva|bcg|bcn|beats|beauty"
+        + "|beer|bentley|berlin|best|bestbuy|bet|bharti|bible|bid|bike|bing|bingo|bio|biz|black"
+        + "|blackfriday|blockbuster|blog|bloomberg|blue|bms|bmw|bnpparibas|boats|boehringer|bofa"
+        + "|bom|bond|boo|book|booking|bosch|bostik|boston|bot|boutique|box|bradesco|bridgestone"
+        + "|broadway|broker|brother|brussels|build|builders|business|buy|buzz|bzh|b[abdefghijmnorstvwyz])"
+        + "|(?:cab|cafe|cal|call|calvinklein|cam|camera|camp|canon|capetown|capital|capitalone"
+        + "|car|caravan|cards|care|career|careers|cars|casa|case|cash|casino|cat|catering|catholic"
+        + "|cba|cbn|cbre|cbs|center|ceo|cern|cfa|cfd|chanel|channel|charity|chase|chat|cheap|chintai"
+        + "|christmas|chrome|church|cipriani|circle|cisco|citadel|citi|citic|city|cityeats|claims"
+        + "|cleaning|click|clinic|clinique|clothing|cloud|club|clubmed|coach|codes|coffee|college"
+        + "|cologne|com|comcast|commbank|community|company|compare|computer|comsec|condos|construction"
+        + "|consulting|contact|contractors|cooking|cool|coop|corsica|country|coupon|coupons|courses"
+        + "|cpa|credit|creditcard|creditunion|cricket|crown|crs|cruise|cruises|cuisinella|cymru"
+        + "|cyou|c[acdfghiklmnoruvwxyz])"
+        + "|(?:dabur|dad|dance|data|date|dating|datsun|day|dclk|dds|deal|dealer|deals|degree"
+        + "|delivery|dell|deloitte|delta|democrat|dental|dentist|desi|design|dev|dhl|diamonds|diet"
+        + "|digital|direct|directory|discount|discover|dish|diy|dnp|docs|doctor|dog|domains|dot"
+        + "|download|drive|dtv|dubai|dunlop|dupont|durban|dvag|dvr|d[ejkmoz])"
+        + "|(?:earth|eat|eco|edeka|edu|education|email|emerck|energy|engineer|engineering|enterprises"
+        + "|epson|equipment|ericsson|erni|esq|estate|etisalat|eurovision|eus|events|exchange|expert"
+        + "|exposed|express|extraspace|e[cegrstu])"
+        + "|(?:fage|fail|fairwinds|faith|family|fan|fans|farm|farmers|fashion|fast|fedex|feedback"
+        + "|ferrari|ferrero|fidelity|fido|film|final|finance|financial|fire|firestone|firmdale"
+        + "|fish|fishing|fit|fitness|flickr|flights|flir|florist|flowers|fly|foo|food|football"
+        + "|ford|forex|forsale|forum|foundation|fox|free|fresenius|frl|frogans|frontdoor|frontier"
+        + "|ftr|fujitsu|fun|fund|furniture|futbol|fyi|f[ijkmor])"
+        + "|(?:gal|gallery|gallo|gallup|game|games|gap|garden|gay|gbiz|gdn|gea|gent|genting"
+        + "|george|ggee|gift|gifts|gives|giving|glass|gle|global|globo|gmail|gmbh|gmo|gmx|godaddy"
+        + "|gold|goldpoint|golf|goo|goodyear|goog|google|gop|got|gov|grainger|graphics|gratis|green"
+        + "|gripe|grocery|group|guardian|gucci|guge|guide|guitars|guru|g[abdefghilmnpqrstuwy])"
+        + "|(?:hair|hamburg|hangout|haus|hbo|hdfc|hdfcbank|health|healthcare|help|helsinki|here"
+        + "|hermes|hiphop|hisamitsu|hitachi|hiv|hkt|hockey|holdings|holiday|homedepot|homegoods"
+        + "|homes|homesense|honda|horse|hospital|host|hosting|hot|hotels|hotmail|house|how|hsbc"
+        + "|hughes|hyatt|hyundai|h[kmnrtu])"
+        + "|(?:ibm|icbc|ice|icu|ieee|ifm|ikano|imamat|imdb|immo|immobilien|inc|industries|infiniti"
+        + "|info|ing|ink|institute|insurance|insure|int|international|intuit|investments|ipiranga"
+        + "|irish|ismaili|ist|istanbul|itau|itv|i[delmnoqrst])"
+        + "|(?:jaguar|java|jcb|jeep|jetzt|jewelry|jio|jll|jmp|jnj|jobs|joburg|jot|joy|jpmorgan"
+        + "|jprs|juegos|juniper|j[emop])"
+        + "|(?:kaufen|kddi|kerryhotels|kerrylogistics|kerryproperties|kfh|kia|kids|kim|kinder"
+        + "|kindle|kitchen|kiwi|koeln|komatsu|kosher|kpmg|kpn|krd|kred|kuokgroup|kyoto|k[eghimnprwyz])"
+        + "|(?:lacaixa|lamborghini|lamer|lancaster|land|landrover|lanxess|lasalle|lat|latino"
+        + "|latrobe|law|lawyer|lds|lease|leclerc|lefrak|legal|lego|lexus|lgbt|lidl|life|lifeinsurance"
+        + "|lifestyle|lighting|like|lilly|limited|limo|lincoln|link|lipsy|live|living|llc|llp|loan"
+        + "|loans|locker|locus|lol|london|lotte|lotto|love|lpl|lplfinancial|ltd|ltda|lundbeck|luxe"
+        + "|luxury|l[abcikrstuvy])"
+        + "|(?:madrid|maif|maison|makeup|man|management|mango|map|market|marketing|markets|marriott"
+        + "|marshalls|mattel|mba|mckinsey|med|media|meet|melbourne|meme|memorial|men|menu|merckmsd"
+        + "|miami|microsoft|mil|mini|mint|mit|mitsubishi|mlb|mls|mma|mobi|mobile|moda|moe|moi|mom"
+        + "|monash|money|monster|mormon|mortgage|moscow|moto|motorcycles|mov|movie|msd|mtn|mtr"
+        + "|museum|music|m[acdeghklmnopqrstuvwxyz])"
+        + "|(?:nab|nagoya|name|natura|navy|nba|nec|net|netbank|netflix|network|neustar|new|news"
+        + "|next|nextdirect|nexus|nfl|ngo|nhk|nico|nike|nikon|ninja|nissan|nissay|nokia|norton"
+        + "|now|nowruz|nowtv|nra|nrw|ntt|nyc|n[acefgilopruz])"
+        + "|(?:obi|observer|office|okinawa|olayan|olayangroup|oldnavy|ollo|omega|one|ong|onl"
+        + "|online|ooo|open|oracle|orange|org|organic|origins|osaka|otsuka|ott|ovh|om)"
+        + "|(?:page|panasonic|paris|pars|partners|parts|party|pay|pccw|pet|pfizer|pharmacy|phd"
+        + "|philips|phone|photo|photography|photos|physio|pics|pictet|pictures|pid|pin|ping|pink"
+        + "|pioneer|pizza|place|play|playstation|plumbing|plus|pnc|pohl|poker|politie|porn|post"
+        + "|pramerica|praxi|press|prime|pro|prod|productions|prof|progressive|promo|properties"
+        + "|property|protection|pru|prudential|pub|pwc|p[aefghklmnrstwy])"
+        + "|(?:qpon|quebec|quest|qa)"
+        + "|(?:racing|radio|read|realestate|realtor|realty|recipes|red|redstone|redumbrella"
+        + "|rehab|reise|reisen|reit|reliance|ren|rent|rentals|repair|report|republican|rest|restaurant"
+        + "|review|reviews|rexroth|rich|richardli|ricoh|ril|rio|rip|rocher|rocks|rodeo|rogers|room"
+        + "|rsvp|rugby|ruhr|run|rwe|ryukyu|r[eosuw])"
+        + "|(?:saarland|safe|safety|sakura|sale|salon|samsclub|samsung|sandvik|sandvikcoromant"
+        + "|sanofi|sap|sarl|sas|save|saxo|sbi|sbs|sca|scb|schaeffler|schmidt|scholarships|school"
+        + "|schule|schwarz|science|scot|search|seat|secure|security|seek|select|sener|services"
+        + "|seven|sew|sex|sexy|sfr|shangrila|sharp|shaw|shell|shia|shiksha|shoes|shop|shopping"
+        + "|shouji|show|showtime|silk|sina|singles|site|ski|skin|sky|skype|sling|smart|smile|sncf"
+        + "|soccer|social|softbank|software|sohu|solar|solutions|song|sony|soy|spa|space|sport"
+        + "|spot|srl|stada|staples|star|statebank|statefarm|stc|stcgroup|stockholm|storage|store"
+        + "|stream|studio|study|style|sucks|supplies|supply|support|surf|surgery|suzuki|swatch"
+        + "|swiss|sydney|systems|s[abcdeghijklmnorstuvxyz])"
+        + "|(?:tab|taipei|talk|taobao|target|tatamotors|tatar|tattoo|tax|taxi|tci|tdk|team|tech"
+        + "|technology|tel|temasek|tennis|teva|thd|theater|theatre|tiaa|tickets|tienda|tips|tires"
+        + "|tirol|tjmaxx|tjx|tkmaxx|tmall|today|tokyo|tools|top|toray|toshiba|total|tours|town"
+        + "|toyota|toys|trade|trading|training|travel|travelers|travelersinsurance|trust|trv|tube"
+        + "|tui|tunes|tushu|tvs|t[cdfghjklmnortvwz])"
+        + "|(?:ubank|ubs|unicom|university|uno|uol|ups|u[agksyz])"
+        + "|(?:vacations|vana|vanguard|vegas|ventures|verisign|versicherung|vet|viajes|video"
+        + "|vig|viking|villas|vin|vip|virgin|visa|vision|viva|vivo|vlaanderen|vodka|volkswagen"
+        + "|volvo|vote|voting|voto|voyage|v[aceginu])"
+        + "|(?:wales|walmart|walter|wang|wanggou|watch|watches|weather|weatherchannel|webcam"
+        + "|weber|website|wed|wedding|weibo|weir|whoswho|wien|wiki|williamhill|win|windows|wine"
+        + "|winners|wme|wolterskluwer|woodside|work|works|world|wow|wtc|wtf|w[fs])"
+        + "|(?:\u03b5\u03bb|\u03b5\u03c5|\u0431\u0433|\u0431\u0435\u043b|\u0434\u0435\u0442\u0438"
+        + "|\u0435\u044e|\u043a\u0430\u0442\u043e\u043b\u0438\u043a|\u043a\u043e\u043c|\u043c\u043a\u0434"
         + "|\u043c\u043e\u043d|\u043c\u043e\u0441\u043a\u0432\u0430|\u043e\u043d\u043b\u0430\u0439\u043d"
         + "|\u043e\u0440\u0433|\u0440\u0443\u0441|\u0440\u0444|\u0441\u0430\u0439\u0442|\u0441\u0440\u0431"
-        + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05e7\u05d5\u05dd|\u0627\u0631\u0627\u0645\u0643\u0648"
-        + "|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
-        + "|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0627\u06cc\u0631\u0627\u0646"
-        + "|\u0628\u0627\u0632\u0627\u0631|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633"
-        + "|\u0633\u0648\u062f\u0627\u0646|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629"
-        + "|\u0639\u0631\u0627\u0642|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646"
-        + "|\u0642\u0637\u0631|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627"
-        + "|\u0645\u0648\u0642\u0639|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
-        + "|\u0938\u0902\u0917\u0920\u0928|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4"
-        + "|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
-        + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22"
-        + "|\u10d2\u10d4|\u307f\u3093\u306a|\u30b0\u30fc\u30b0\u30eb|\u30b3\u30e0|\u4e16\u754c"
-        + "|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51|\u4f01\u4e1a|\u4f5b\u5c71"
-        + "|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063"
-        + "|\u5546\u57ce|\u5546\u5e97|\u5546\u6807|\u5728\u7ebf|\u5927\u62ff|\u5a31\u4e50|\u5de5\u884c"
-        + "|\u5e7f\u4e1c|\u6148\u5584|\u6211\u7231\u4f60|\u624b\u673a|\u653f\u52a1|\u653f\u5e9c"
-        + "|\u65b0\u52a0\u5761|\u65b0\u95fb|\u65f6\u5c1a|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f"
-        + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7edc"
-        + "|\u8c37\u6b4c|\u96c6\u56e2|\u98de\u5229\u6d66|\u9910\u5385|\u9999\u6e2f|\ub2f7\ub137"
-        + "|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d|xbox"
-        + "|xerox|xin|xn\\-\\-11b4c3d|xn\\-\\-1qqw23a|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g"
-        + "|xn\\-\\-3e0b707e|xn\\-\\-3pxu8k|xn\\-\\-42c2d9a|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4gbrim"
-        + "|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks"
-        + "|xn\\-\\-80ao21a|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-90a3ac|xn\\-\\-90ais|xn\\-\\-9dbq2a"
-        + "|xn\\-\\-9et52u|xn\\-\\-b4w605ferd|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd"
-        + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-efvy88h"
-        + "|xn\\-\\-estv75g|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s"
-        + "|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-gecrj9c"
-        + "|xn\\-\\-h2brj9c|xn\\-\\-hxt814e|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i"
-        + "|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d"
+        + "|\u0443\u043a\u0440|\u049b\u0430\u0437|\u0570\u0561\u0575|\u05d9\u05e9\u05e8\u05d0\u05dc"
+        + "|\u05e7\u05d5\u05dd|\u0627\u0628\u0648\u0638\u0628\u064a|\u0627\u062a\u0635\u0627\u0644\u0627\u062a"
+        + "|\u0627\u0631\u0627\u0645\u0643\u0648|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u0628\u062d\u0631\u064a\u0646"
+        + "|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629"
+        + "|\u0627\u0644\u0639\u0644\u064a\u0627\u0646|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a"
+        + "|\u0627\u06cc\u0631\u0627\u0646|\u0628\u0627\u0631\u062a|\u0628\u0627\u0632\u0627\u0631"
+        + "|\u0628\u064a\u062a\u0643|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u062f\u0627\u0646"
+        + "|\u0633\u0648\u0631\u064a\u0629|\u0634\u0628\u0643\u0629|\u0639\u0631\u0627\u0642|\u0639\u0631\u0628"
+        + "|\u0639\u0645\u0627\u0646|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0643\u0627\u062b\u0648\u0644\u064a\u0643"
+        + "|\u0643\u0648\u0645|\u0645\u0635\u0631|\u0645\u0644\u064a\u0633\u064a\u0627|\u0645\u0648\u0631\u064a\u062a\u0627\u0646\u064a\u0627"
+        + "|\u0645\u0648\u0642\u0639|\u0647\u0645\u0631\u0627\u0647|\u067e\u0627\u06a9\u0633\u062a\u0627\u0646"
+        + "|\u0680\u0627\u0631\u062a|\u0915\u0949\u092e|\u0928\u0947\u091f|\u092d\u093e\u0930\u0924"
+        + "|\u092d\u093e\u0930\u0924\u092e\u094d|\u092d\u093e\u0930\u094b\u0924|\u0938\u0902\u0917\u0920\u0928"
+        + "|\u09ac\u09be\u0982\u09b2\u09be|\u09ad\u09be\u09b0\u09a4|\u09ad\u09be\u09f0\u09a4|\u0a2d\u0a3e\u0a30\u0a24"
+        + "|\u0aad\u0abe\u0ab0\u0aa4|\u0b2d\u0b3e\u0b30\u0b24|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe"
+        + "|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd"
+        + "|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0cad\u0cbe\u0cb0\u0ca4|\u0d2d\u0d3e\u0d30\u0d24\u0d02"
+        + "|\u0dbd\u0d82\u0d9a\u0dcf|\u0e04\u0e2d\u0e21|\u0e44\u0e17\u0e22|\u0ea5\u0eb2\u0ea7|\u10d2\u10d4"
+        + "|\u307f\u3093\u306a|\u30a2\u30de\u30be\u30f3|\u30af\u30e9\u30a6\u30c9|\u30b0\u30fc\u30b0\u30eb"
+        + "|\u30b3\u30e0|\u30b9\u30c8\u30a2|\u30bb\u30fc\u30eb|\u30d5\u30a1\u30c3\u30b7\u30e7\u30f3"
+        + "|\u30dd\u30a4\u30f3\u30c8|\u4e16\u754c|\u4e2d\u4fe1|\u4e2d\u56fd|\u4e2d\u570b|\u4e2d\u6587\u7f51"
+        + "|\u4e9a\u9a6c\u900a|\u4f01\u4e1a|\u4f5b\u5c71|\u4fe1\u606f|\u5065\u5eb7|\u516b\u5366"
+        + "|\u516c\u53f8|\u516c\u76ca|\u53f0\u6e7e|\u53f0\u7063|\u5546\u57ce|\u5546\u5e97|\u5546\u6807"
+        + "|\u5609\u91cc|\u5609\u91cc\u5927\u9152\u5e97|\u5728\u7ebf|\u5927\u62ff|\u5929\u4e3b\u6559"
+        + "|\u5a31\u4e50|\u5bb6\u96fb|\u5e7f\u4e1c|\u5fae\u535a|\u6148\u5584|\u6211\u7231\u4f60"
+        + "|\u624b\u673a|\u62db\u8058|\u653f\u52a1|\u653f\u5e9c|\u65b0\u52a0\u5761|\u65b0\u95fb"
+        + "|\u65f6\u5c1a|\u66f8\u7c4d|\u673a\u6784|\u6de1\u9a6c\u9521|\u6e38\u620f|\u6fb3\u9580"
+        + "|\u70b9\u770b|\u79fb\u52a8|\u7ec4\u7ec7\u673a\u6784|\u7f51\u5740|\u7f51\u5e97|\u7f51\u7ad9"
+        + "|\u7f51\u7edc|\u8054\u901a|\u8c37\u6b4c|\u8d2d\u7269|\u901a\u8ca9|\u96c6\u56e2|\u96fb\u8a0a\u76c8\u79d1"
+        + "|\u98de\u5229\u6d66|\u98df\u54c1|\u9910\u5385|\u9999\u683c\u91cc\u62c9|\u9999\u6e2f"
+        + "|\ub2f7\ub137|\ub2f7\ucef4|\uc0bc\uc131|\ud55c\uad6d"
+        + "|xbox|xerox|xfinity|xihuan|xin|xn\\-\\-11b4c3d|xn\\-\\-1ck2e1b|xn\\-\\-1qqw23a|xn\\-\\-2scrj9c"
+        + "|xn\\-\\-30rr7y|xn\\-\\-3bst00m|xn\\-\\-3ds443g|xn\\-\\-3e0b707e|xn\\-\\-3hcrj9c|xn\\-\\-3pxu8k"
+        + "|xn\\-\\-42c2d9a|xn\\-\\-45br5cyl|xn\\-\\-45brj9c|xn\\-\\-45q11c|xn\\-\\-4dbrk0ce|xn\\-\\-4gbrim"
+        + "|xn\\-\\-54b7fta0cc|xn\\-\\-55qw42g|xn\\-\\-55qx5d|xn\\-\\-5su34j936bgsg|xn\\-\\-5tzm5g"
+        + "|xn\\-\\-6frz82g|xn\\-\\-6qq986b3xl|xn\\-\\-80adxhks|xn\\-\\-80ao21a|xn\\-\\-80aqecdr1a"
+        + "|xn\\-\\-80asehdb|xn\\-\\-80aswg|xn\\-\\-8y0a063a|xn\\-\\-90a3ac|xn\\-\\-90ae|xn\\-\\-90ais"
+        + "|xn\\-\\-9dbq2a|xn\\-\\-9et52u|xn\\-\\-9krt00a|xn\\-\\-b4w605ferd|xn\\-\\-bck1b9a5dre4c"
+        + "|xn\\-\\-c1avg|xn\\-\\-c2br7g|xn\\-\\-cck2b3b|xn\\-\\-cckwcxetd|xn\\-\\-cg4bki|xn\\-\\-clchc0ea0b2g2a9gcd"
+        + "|xn\\-\\-czr694b|xn\\-\\-czrs0t|xn\\-\\-czru2d|xn\\-\\-d1acj3b|xn\\-\\-d1alf|xn\\-\\-e1a4c"
+        + "|xn\\-\\-eckvdtc9d|xn\\-\\-efvy88h|xn\\-\\-fct429k|xn\\-\\-fhbei|xn\\-\\-fiq228c5hs"
+        + "|xn\\-\\-fiq64b|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fjq720a|xn\\-\\-flw351e|xn\\-\\-fpcrj9c3d"
+        + "|xn\\-\\-fzc2c9e2c|xn\\-\\-fzys8d69uvgm|xn\\-\\-g2xx48c|xn\\-\\-gckr3f0f|xn\\-\\-gecrj9c"
+        + "|xn\\-\\-gk3at1e|xn\\-\\-h2breg3eve|xn\\-\\-h2brj9c|xn\\-\\-h2brj9c8c|xn\\-\\-hxt814e"
+        + "|xn\\-\\-i1b6b1a6a2e|xn\\-\\-imr513n|xn\\-\\-io0a7i|xn\\-\\-j1aef|xn\\-\\-j1amh|xn\\-\\-j6w193g"
+        + "|xn\\-\\-jlq480n2rg|xn\\-\\-jvr189m|xn\\-\\-kcrx77d1x4a|xn\\-\\-kprw13d|xn\\-\\-kpry57d"
         + "|xn\\-\\-kput3i|xn\\-\\-l1acc|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgb9awbf|xn\\-\\-mgba3a3ejt"
-        + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e"
-        + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbpl2fh|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab"
-        + "|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema"
-        + "|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh"
-        + "|xn\\-\\-pssy2u|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-s9brj9c"
-        + "|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb"
-        + "|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv|xn\\-\\-vuq861b|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a"
+        + "|xn\\-\\-mgba3a4f16a|xn\\-\\-mgba7c0bbn0a|xn\\-\\-mgbaakc7dvf|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbab2bd"
+        + "|xn\\-\\-mgbah1a3hjkrd|xn\\-\\-mgbai9azgqp6j|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a|xn\\-\\-mgbbh1a71e"
+        + "|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgbca7dzdo|xn\\-\\-mgbcpq6gpa1a|xn\\-\\-mgberp4a5d4ar|xn\\-\\-mgbgu82a"
+        + "|xn\\-\\-mgbi4ecexp|xn\\-\\-mgbpl2fh|xn\\-\\-mgbt3dhd|xn\\-\\-mgbtx2b|xn\\-\\-mgbx4cd0ab"
+        + "|xn\\-\\-mix891f|xn\\-\\-mk1bu44c|xn\\-\\-mxtq1m|xn\\-\\-ngbc5azd|xn\\-\\-ngbe9e0a|xn\\-\\-ngbrx"
+        + "|xn\\-\\-node|xn\\-\\-nqv7f|xn\\-\\-nqv7fs00ema|xn\\-\\-nyqy26a|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl"
+        + "|xn\\-\\-otu796d|xn\\-\\-p1acf|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-pssy2u|xn\\-\\-q7ce6a"
+        + "|xn\\-\\-q9jyb4c|xn\\-\\-qcka1pmc|xn\\-\\-qxa6a|xn\\-\\-qxam|xn\\-\\-rhqv96g|xn\\-\\-rovu88b"
+        + "|xn\\-\\-rvc1e0am3e|xn\\-\\-s9brj9c|xn\\-\\-ses554g|xn\\-\\-t60b56a|xn\\-\\-tckwe|xn\\-\\-tiq49xqyj"
+        + "|xn\\-\\-unup4y|xn\\-\\-vermgensberater\\-ctb|xn\\-\\-vermgensberatung\\-pwb|xn\\-\\-vhquv"
+        + "|xn\\-\\-vuq861b|xn\\-\\-w4r85el8fhu5dnra|xn\\-\\-w4rs40l|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a"
         + "|xn\\-\\-xhq521b|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-y9a3aq|xn\\-\\-yfro4i67o"
-        + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xperia|xxx|xyz)"
-        + "|(?:yachts|yamaxun|yandex|yodobashi|yoga|yokohama|youtube|y[et])"
-        + "|(?:zara|zip|zone|zuerich|z[amw]))";
-
+        + "|xn\\-\\-ygbi2ammx|xn\\-\\-zfr164b|xxx|xyz)"
+        + "|(?:yachts|yahoo|yamaxun|yandex|yodobashi|yoga|yokohama|you|youtube|yun|y[et])"
+        + "|(?:zappos|zara|zero|zip|zone|zuerich|z[amw]))";
     /**
      * Kept for backward compatibility reasons.
      *
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index bc83750..2761aae 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -35,8 +35,6 @@
  * @hide
  */
 public final class InputWindowHandle {
-    // TODO (b/300094445): Convert to use correct flagging infrastructure
-    public static final boolean USE_SURFACE_TRUSTED_OVERLAY = true;
 
     /**
      * An internal annotation for all the {@link android.os.InputConfig} flags that can be
@@ -61,6 +59,7 @@
             InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
             InputConfig.IS_WALLPAPER,
             InputConfig.PAUSE_DISPATCHING,
+            InputConfig.TRUSTED_OVERLAY,
             InputConfig.WATCH_OUTSIDE_TOUCH,
             InputConfig.SLIPPERY,
             InputConfig.DISABLE_USER_ACTIVITY,
@@ -273,13 +272,4 @@
         }
         this.inputConfig &= ~inputConfig;
     }
-
-    public void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
-            boolean isTrusted) {
-        if (USE_SURFACE_TRUSTED_OVERLAY) {
-            t.setTrustedOverlay(sc, isTrusted);
-        } else if (isTrusted) {
-            inputConfig |= InputConfig.TRUSTED_OVERLAY;
-        }
-    }
 }
diff --git a/core/java/android/view/KeyCharacterMap.aidl b/core/java/android/view/KeyCharacterMap.aidl
new file mode 100644
index 0000000..1a761a67
--- /dev/null
+++ b/core/java/android/view/KeyCharacterMap.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, 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.view;
+
+parcelable KeyCharacterMap;
\ No newline at end of file
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index d8221a6..4fe53c2 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputManagerGlobal;
@@ -309,6 +310,10 @@
     private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
     private static native boolean nativeEquals(long ptr1, long ptr2);
 
+    private static native void nativeApplyOverlay(long ptr, String layoutDescriptor,
+            String overlay);
+    private static native int nativeGetMappedKey(long ptr, int scanCode);
+
     private KeyCharacterMap(Parcel in) {
         if (in == null) {
             throw new IllegalArgumentException("parcel must not be null");
@@ -368,6 +373,38 @@
     }
 
     /**
+     * Loads the key character map with applied KCM overlay.
+     *
+     * @param layoutDescriptor descriptor of the applied overlay KCM
+     * @param overlay          string describing the overlay KCM
+     * @return The resultant key character map.
+     * @throws {@link UnavailableException} if the key character map
+     *                could not be loaded because it was malformed or the default key character map
+     *                is missing from the system.
+     * @hide
+     */
+    public static KeyCharacterMap load(@NonNull String layoutDescriptor, @NonNull String overlay) {
+        KeyCharacterMap kcm = KeyCharacterMap.load(VIRTUAL_KEYBOARD);
+        kcm.applyOverlay(layoutDescriptor, overlay);
+        return kcm;
+    }
+
+    private void applyOverlay(@NonNull String layoutDescriptor, @NonNull String overlay) {
+        nativeApplyOverlay(mPtr, layoutDescriptor, overlay);
+    }
+
+    /**
+     * Gets the mapped key for the provided scan code. Returns the provided default if no mapping
+     * found in the KeyCharacterMap.
+     *
+     * @hide
+     */
+    public int getMappedKeyOrDefault(int scanCode, int defaultKeyCode) {
+        int keyCode = nativeGetMappedKey(mPtr, scanCode);
+        return keyCode == KeyEvent.KEYCODE_UNKNOWN ? defaultKeyCode : keyCode;
+    }
+
+    /**
      * Gets the Unicode character generated by the specified key and meta
      * key state combination.
      * <p>
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index cdf5eec3..776eeda 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -18,7 +18,6 @@
 
 import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
 import static android.view.Display.DEFAULT_DISPLAY;
-
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
@@ -4361,6 +4360,17 @@
         public boolean isResampled;
 
         /**
+         * Returns true if this pointer coords object was generated by resampling, rather than from
+         * an actual input event from the device at this time.
+         *
+         * @hide
+         */
+        @TestApi
+        public boolean isResampled() {
+            return isResampled;
+        }
+
+        /**
          * Clears the contents of this object.
          * Resets all axes to zero.
          */
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 85d7c10..fe515cd 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4437,7 +4437,7 @@
      * @param drawingPosition the drawing order position.
      * @return the container position of a child for this drawing order position.
      *
-     * @see #getChildDrawingOrder(int, int)}
+     * @see #getChildDrawingOrder(int, int)
      */
     public final int getChildDrawingOrder(int drawingPosition) {
         return getChildDrawingOrder(getChildCount(), drawingPosition);
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 3b8298e..dda3993 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -124,16 +124,16 @@
                     || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
                 final Insets systemBarsInsets = state.calculateInsets(
                         displayFrame, systemBars(), requestedVisibleTypes);
-                if (systemBarsInsets.left > 0) {
+                if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) {
                     displayCutoutSafeExceptMaybeBars.left = MIN_X;
                 }
-                if (systemBarsInsets.top > 0) {
+                if (systemBarsInsets.top >= cutout.getSafeInsetTop()) {
                     displayCutoutSafeExceptMaybeBars.top = MIN_Y;
                 }
-                if (systemBarsInsets.right > 0) {
+                if (systemBarsInsets.right >= cutout.getSafeInsetRight()) {
                     displayCutoutSafeExceptMaybeBars.right = MAX_X;
                 }
-                if (systemBarsInsets.bottom > 0) {
+                if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) {
                     displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
                 }
             }
diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING
index 4b2ea1a..ad59463 100644
--- a/core/java/android/view/inputmethod/TEST_MAPPING
+++ b/core/java/android/view/inputmethod/TEST_MAPPING
@@ -11,6 +11,9 @@
         },
         {
           "exclude-annotation": "android.platform.test.annotations.AppModeFull"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
         }
       ]
     }
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 065089f5..53c73c6 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,10 +16,7 @@
 
 package android.widget;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.TypedArray;
 import android.os.Message;
 import android.util.AttributeSet;
@@ -48,7 +45,6 @@
     private boolean mRunning = false;
     private boolean mStarted = false;
     private boolean mVisible = false;
-    private boolean mUserPresent = true;
     private boolean mAdvancedByHost = false;
 
     public AdapterViewFlipper(Context context) {
@@ -82,40 +78,10 @@
         a.recycle();
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mUserPresent = false;
-                updateRunning();
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                mUserPresent = true;
-                updateRunning(false);
-            }
-        }
-    };
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        // Listen for broadcasts related to user-presence
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
-
-        // OK, this is gross but needed. This class is supported by the
-        // remote views machanism and as a part of that the remote views
-        // can be inflated by a context for another user without the app
-        // having interact users permission - just for loading resources.
-        // For exmaple, when adding widgets from a user profile to the
-        // home screen. Therefore, we register the receiver as the current
-        // user not the one the context is for.
-        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
-                filter, null, getHandler());
-
-
         if (mAutoStart) {
             // Automatically start when requested
             startFlipping();
@@ -126,8 +92,6 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mVisible = false;
-
-        getContext().unregisterReceiver(mReceiver);
         updateRunning();
     }
 
@@ -235,8 +199,7 @@
      *            true.
      */
     private void updateRunning(boolean flipNow) {
-        boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
-                && mAdapter != null;
+        boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null;
         if (running != mRunning) {
             if (running) {
                 showOnly(mWhichChild, flipNow);
@@ -248,7 +211,7 @@
         }
         if (LOGD) {
             Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
-                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+                    + ", mRunning=" + mRunning);
         }
     }
 
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 1f0e95e..e0158332 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -23,7 +23,6 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.BlendMode;
@@ -37,6 +36,9 @@
 import android.view.View;
 import android.view.inspector.InspectableProperty;
 import android.widget.RemoteViews.RemoteView;
+import android.widget.TextClock.ClockEventDelegate;
+
+import com.android.internal.util.Preconditions;
 
 import java.time.Clock;
 import java.time.DateTimeException;
@@ -112,6 +114,7 @@
     public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
 
+        mClockEventDelegate = new ClockEventDelegate(context);
         mSecondsHandFps = AppGlobals.getIntCoreSetting(
                 WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS,
                 context.getResources()
@@ -584,21 +587,9 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        IntentFilter filter = new IntentFilter();
 
         if (!mReceiverAttached) {
-            filter.addAction(Intent.ACTION_TIME_CHANGED);
-            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
-            // OK, this is gross but needed. This class is supported by the
-            // remote views mechanism and as a part of that the remote views
-            // can be inflated by a context for another user without the app
-            // having interact users permission - just for loading resources.
-            // For example, when adding widgets from a user profile to the
-            // home screen. Therefore, we register the receiver as the current
-            // user not the one the context is for.
-            getContext().registerReceiverAsUser(mIntentReceiver,
-                    android.os.Process.myUserHandle(), filter, null, getHandler());
+            mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
             mReceiverAttached = true;
         }
 
@@ -615,12 +606,23 @@
     @Override
     protected void onDetachedFromWindow() {
         if (mReceiverAttached) {
-            getContext().unregisterReceiver(mIntentReceiver);
+            mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
             mReceiverAttached = false;
         }
         super.onDetachedFromWindow();
     }
 
+    /**
+     * Sets a delegate to handle clock event registration. This must be called before the view is
+     * attached to the window
+     *
+     * @hide
+     */
+    public void setClockEventDelegate(ClockEventDelegate delegate) {
+        Preconditions.checkState(!mReceiverAttached, "Clock events already registered");
+        mClockEventDelegate = delegate;
+    }
+
     private void onVisible() {
         if (!mVisible) {
             mVisible = true;
@@ -797,6 +799,7 @@
         }
     };
     private boolean mReceiverAttached;
+    private ClockEventDelegate mClockEventDelegate;
 
     private final Runnable mTick = new Runnable() {
         @Override
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index e48afb2..255bd67 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.os.Process.myUserHandle;
 import static android.view.ViewDebug.ExportedProperty;
 import static android.widget.RemoteViews.RemoteView;
 
@@ -24,7 +25,6 @@
 import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -43,6 +43,7 @@
 import android.view.inspector.InspectableProperty;
 
 import com.android.internal.R;
+import com.android.internal.util.Preconditions;
 
 import java.time.Duration;
 import java.time.Instant;
@@ -141,6 +142,8 @@
     private boolean mRegistered;
     private boolean mShouldRunTicker;
 
+    private ClockEventDelegate mClockEventDelegate;
+
     private Calendar mTime;
     private String mTimeZone;
 
@@ -178,8 +181,7 @@
             if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
                 final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
                 createTime(timeZone);
-            } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction())
-                    || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) {
+            } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
                 return;
             }
             onTimeChanged();
@@ -282,6 +284,7 @@
         if (mFormat24 == null) {
             mFormat24 = getBestDateTimePattern("Hm");
         }
+        mClockEventDelegate = new ClockEventDelegate(getContext());
 
         createTime(mTimeZone);
         chooseFormat();
@@ -431,6 +434,17 @@
     }
 
     /**
+     * Sets a delegate to handle clock event registration. This must be called before the view is
+     * attached to the window
+     *
+     * @hide
+     */
+    public void setClockEventDelegate(ClockEventDelegate delegate) {
+        Preconditions.checkState(!mRegistered, "Clock events already registered");
+        mClockEventDelegate = delegate;
+    }
+
+    /**
      * Update the displayed time if necessary and invalidate the view.
      */
     public void refreshTime() {
@@ -557,7 +571,7 @@
         if (!mRegistered) {
             mRegistered = true;
 
-            registerReceiver();
+            mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
             registerObserver();
 
             createTime(mTimeZone);
@@ -582,7 +596,7 @@
         super.onDetachedFromWindow();
 
         if (mRegistered) {
-            unregisterReceiver();
+            mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
             unregisterObserver();
 
             mRegistered = false;
@@ -598,56 +612,27 @@
         mStopTicking = true;
     }
 
-    private void registerReceiver() {
-        final IntentFilter filter = new IntentFilter();
-
-        filter.addAction(Intent.ACTION_TIME_CHANGED);
-        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
-        // OK, this is gross but needed. This class is supported by the
-        // remote views mechanism and as a part of that the remote views
-        // can be inflated by a context for another user without the app
-        // having interact users permission - just for loading resources.
-        // For example, when adding widgets from a managed profile to the
-        // home screen. Therefore, we register the receiver as the user
-        // the app is running as not the one the context is for.
-        getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(),
-                filter, null, getHandler());
-    }
-
     private void registerObserver() {
         if (mRegistered) {
             if (mFormatChangeObserver == null) {
                 mFormatChangeObserver = new FormatChangeObserver(getHandler());
             }
-            final ContentResolver resolver = getContext().getContentResolver();
-            Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
-            if (mShowCurrentUserTime) {
-                resolver.registerContentObserver(uri, true,
-                        mFormatChangeObserver, UserHandle.USER_ALL);
-            } else {
-                // UserHandle.myUserId() is needed. This class is supported by the
-                // remote views mechanism and as a part of that the remote views
-                // can be inflated by a context for another user without the app
-                // having interact users permission - just for loading resources.
-                // For example, when adding widgets from a managed profile to the
-                // home screen. Therefore, we register the ContentObserver with the user
-                // the app is running (e.g. the launcher) and not the user of the
-                // context (e.g. the widget's profile).
-                resolver.registerContentObserver(uri, true,
-                        mFormatChangeObserver, UserHandle.myUserId());
-            }
+            // UserHandle.myUserId() is needed. This class is supported by the
+            // remote views mechanism and as a part of that the remote views
+            // can be inflated by a context for another user without the app
+            // having interact users permission - just for loading resources.
+            // For example, when adding widgets from a managed profile to the
+            // home screen. Therefore, we register the ContentObserver with the user
+            // the app is running (e.g. the launcher) and not the user of the
+            // context (e.g. the widget's profile).
+            int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId();
+            mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle);
         }
     }
 
-    private void unregisterReceiver() {
-        getContext().unregisterReceiver(mIntentReceiver);
-    }
-
     private void unregisterObserver() {
         if (mFormatChangeObserver != null) {
-            final ContentResolver resolver = getContext().getContentResolver();
-            resolver.unregisterContentObserver(mFormatChangeObserver);
+            mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver);
         }
     }
 
@@ -674,4 +659,59 @@
         stream.addProperty("format", mFormat == null ? null : mFormat.toString());
         stream.addProperty("hasSeconds", mHasSeconds);
     }
+
+    /**
+     * Utility class to delegate some system event handling to allow overring the default behavior
+     *
+     * @hide
+     */
+    public static class ClockEventDelegate {
+
+        private final Context mContext;
+
+        public ClockEventDelegate(Context context) {
+            mContext = context;
+        }
+
+        /**
+         * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and
+         * {@link Intent#ACTION_TIMEZONE_CHANGED}
+         *
+         * OK, this is gross but needed. This class is supported by the remote views mechanism and
+         * as a part of that the remote views can be inflated by a context for another user without
+         * the app having interact users permission - just for loading resources. For example,
+         * when adding widgets from a managed profile to the home screen. Therefore, we register
+         * the receiver as the user the app is running as not the one the context is for.
+         */
+        public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
+            final IntentFilter filter = new IntentFilter();
+
+            filter.addAction(Intent.ACTION_TIME_CHANGED);
+            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+            mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler);
+        }
+
+        /**
+         * Unregisters a previously registered receiver
+         */
+        public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
+            mContext.unregisterReceiver(receiver);
+        }
+
+        /**
+         * Registers an observer for time format changes
+         */
+        public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+            Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
+            mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle);
+        }
+
+        /**
+         * Unregisters a previously registered observer
+         */
+        public void unregisterFormatChangeObserver(ContentObserver observer) {
+            mContext.getContentResolver().unregisterContentObserver(observer);
+        }
+    }
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 59344b0..ac0e493 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,6 +27,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+
 import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
 
 import android.R;
@@ -240,7 +241,6 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastMath;
 import com.android.internal.util.Preconditions;
-import com.android.text.flags.Flags;
 
 import libcore.util.EmptyArray;
 
@@ -1634,7 +1634,7 @@
         }
 
         if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
-            mUseBoundsForWidth = Flags.useBoundsForWidth();
+            mUseBoundsForWidth = false;  // TODO: Connect to the flag.
         } else {
             mUseBoundsForWidth = false;
         }
@@ -15143,6 +15143,9 @@
 
         final ClipDescription description =
                 getClipboardManagerForUser().getPrimaryClipDescription();
+        if (description == null) {
+            return false;
+        }
         final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
         return (isPlainType && description.isStyledText())
                 || description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 5abb6e1..eaf037e 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -18,10 +18,7 @@
 
 import android.annotation.IntRange;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Message;
@@ -51,8 +48,6 @@
     private boolean mRunning = false;
     private boolean mStarted = false;
     private boolean mVisible = false;
-    @UnsupportedAppUsage
-    private boolean mUserPresent = true;
 
     public ViewFlipper(Context context) {
         super(context);
@@ -70,39 +65,10 @@
         a.recycle();
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
-                mUserPresent = false;
-                updateRunning();
-            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                mUserPresent = true;
-                updateRunning(false);
-            }
-        }
-    };
-
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        // Listen for broadcasts related to user-presence
-        final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_SCREEN_OFF);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
-
-        // OK, this is gross but needed. This class is supported by the
-        // remote views machanism and as a part of that the remote views
-        // can be inflated by a context for another user without the app
-        // having interact users permission - just for loading resources.
-        // For exmaple, when adding widgets from a user profile to the
-        // home screen. Therefore, we register the receiver as the current
-        // user not the one the context is for.
-        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
-                filter, null, getHandler());
-
         if (mAutoStart) {
             // Automatically start when requested
             startFlipping();
@@ -113,8 +79,6 @@
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mVisible = false;
-
-        getContext().unregisterReceiver(mReceiver);
         updateRunning();
     }
 
@@ -186,7 +150,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void updateRunning(boolean flipNow) {
-        boolean running = mVisible && mStarted && mUserPresent;
+        boolean running = mVisible && mStarted;
         if (running != mRunning) {
             if (running) {
                 showOnly(mWhichChild, flipNow);
@@ -198,7 +162,7 @@
         }
         if (LOGD) {
             Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
-                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+                    + ", mRunning=" + mRunning);
         }
     }
 
diff --git a/core/java/android/widget/inline/TEST_MAPPING b/core/java/android/widget/inline/TEST_MAPPING
index 26a5569..82c6f61 100644
--- a/core/java/android/widget/inline/TEST_MAPPING
+++ b/core/java/android/widget/inline/TEST_MAPPING
@@ -8,6 +8,9 @@
         },
         {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
         }
       ]
     }
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 95e9e86..e42193d 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.Build;
-import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
@@ -35,7 +34,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.ObjIntConsumer;
-
 /**
  * Handles display and layer captures for the system.
  *
@@ -45,8 +43,6 @@
     private static final String TAG = "ScreenCapture";
     private static final int SCREENSHOT_WAIT_TIME_S = 4 * Build.HW_TIMEOUT_MULTIPLIER;
 
-    private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
-            long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
             long captureListener);
     private static native long nativeCreateScreenCaptureListener(
@@ -56,37 +52,6 @@
     private static native long getNativeListenerFinalizer();
 
     /**
-     * @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);
-    }
-
-    /**
-     * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
-     * the content.
-     *
-     * @hide
-     */
-    public static ScreenshotHardwareBuffer captureDisplay(
-            DisplayCaptureArgs captureArgs) {
-        SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener();
-        int status = captureDisplay(captureArgs, syncScreenCapture);
-        if (status != 0) {
-            return null;
-        }
-
-        try {
-            return syncScreenCapture.getBuffer();
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    /**
      * Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
      *
      * @param layer      The root layer to capture.
@@ -519,92 +484,6 @@
     }
 
     /**
-     * The arguments class used to make display capture requests.
-     *
-     * @hide
-     * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
-     */
-    public static class DisplayCaptureArgs extends CaptureArgs {
-        private final IBinder mDisplayToken;
-        private final int mWidth;
-        private final int mHeight;
-        private final boolean mUseIdentityTransform;
-
-        private DisplayCaptureArgs(Builder builder) {
-            super(builder);
-            mDisplayToken = builder.mDisplayToken;
-            mWidth = builder.mWidth;
-            mHeight = builder.mHeight;
-            mUseIdentityTransform = builder.mUseIdentityTransform;
-        }
-
-        /**
-         * The Builder class used to construct {@link DisplayCaptureArgs}
-         */
-        public static class Builder extends CaptureArgs.Builder<Builder> {
-            private IBinder mDisplayToken;
-            private int mWidth;
-            private int mHeight;
-            private boolean mUseIdentityTransform;
-
-            /**
-             * Construct a new {@link LayerCaptureArgs} with the set parameters. The builder
-             * remains valid.
-             */
-            public DisplayCaptureArgs build() {
-                if (mDisplayToken == null) {
-                    throw new IllegalStateException(
-                            "Can't take screenshot with null display token");
-                }
-                return new DisplayCaptureArgs(this);
-            }
-
-            public Builder(IBinder displayToken) {
-                setDisplayToken(displayToken);
-            }
-
-            /**
-             * The display to take the screenshot of.
-             */
-            public Builder setDisplayToken(IBinder displayToken) {
-                mDisplayToken = displayToken;
-                return this;
-            }
-
-            /**
-             * Set the desired size of the returned buffer. The raw screen  will be  scaled down to
-             * this size
-             *
-             * @param width  The desired width of the returned buffer. Caller may pass in 0 if no
-             *               scaling is desired.
-             * @param height The desired height of the returned buffer. Caller may pass in 0 if no
-             *               scaling is desired.
-             */
-            public Builder setSize(int width, int height) {
-                mWidth = width;
-                mHeight = height;
-                return this;
-            }
-
-            /**
-             * Replace the rotation transform of the display with the identity transformation while
-             * taking the screenshot. This ensures the screenshot is taken in the ROTATION_0
-             * orientation. Set this value to false if the screenshot should be taken in the
-             * current screen orientation.
-             */
-            public Builder setUseIdentityTransform(boolean useIdentityTransform) {
-                mUseIdentityTransform = useIdentityTransform;
-                return this;
-            }
-
-            @Override
-            Builder getThis() {
-                return this;
-            }
-        }
-    }
-
-    /**
      * The arguments class used to make layer capture requests.
      *
      * @hide
@@ -682,7 +561,6 @@
 
     /**
      * The object used to receive the results when invoking screen capture requests via
-     * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
      * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
      *
      * This listener can only be used for a single call to capture content call.
@@ -784,8 +662,7 @@
 
     /**
      * Helper class to synchronously get the {@link ScreenshotHardwareBuffer} when calling
-     * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} or
-     * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)}
+     * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
      */
     public abstract static class SynchronousScreenCaptureListener extends ScreenCaptureListener {
         SynchronousScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) {
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index f40874b..7585826 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -237,13 +237,14 @@
                         PixelFormat.RGBA_8888,
                         GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
                                 | GraphicBuffer.USAGE_SW_WRITE_RARELY);
-                if (background == null) {
+                final Canvas c = background != null ? background.lockCanvas() : null;
+                if (c == null) {
                     Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
                             + mTitle);
+                    mTransaction.clear();
+                    childSurfaceControl.release();
                     return;
                 }
-                // TODO: Support this on HardwareBuffer
-                final Canvas c = background.lockCanvas();
                 drawBackgroundAndBars(c, frame);
                 background.unlockCanvasAndPost(c);
                 mTransaction.setBuffer(mRootSurface,
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index edea297..9b10a7f 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -36,11 +36,17 @@
     private final @WindowManager.TransitionType int mType;
 
     /**
-     * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+     * If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
     private @Nullable ActivityManager.RunningTaskInfo mTriggerTask;
 
+    /**
+     * If non-null, the task containing the pip activity that participates in this
+     * transition.
+     */
+    private @Nullable ActivityManager.RunningTaskInfo mPipTask;
+
     /** If non-null, a remote-transition associated with the source of this transition. */
     private @Nullable RemoteTransition mRemoteTransition;
 
@@ -59,7 +65,8 @@
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition) {
-        this(type, triggerTask, remoteTransition, null /* displayChange */, 0 /* flags */);
+        this(type, triggerTask, null /* pipTask */,
+                remoteTransition, null /* displayChange */, 0 /* flags */);
     }
 
     /** constructor override */
@@ -68,7 +75,17 @@
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
             @Nullable RemoteTransition remoteTransition,
             int flags) {
-        this(type, triggerTask, remoteTransition, null /* displayChange */, flags);
+        this(type, triggerTask, null /* pipTask */,
+                remoteTransition, null /* displayChange */, flags);
+    }
+
+    public TransitionRequestInfo(
+            @WindowManager.TransitionType int type,
+            @Nullable ActivityManager.RunningTaskInfo triggerTask,
+            @Nullable RemoteTransition remoteTransition,
+            @Nullable TransitionRequestInfo.DisplayChange displayChange,
+            int flags) {
+        this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
     }
 
     /** Requested change to a display. */
@@ -246,7 +263,7 @@
         };
 
         @DataClass.Generated(
-                time = 1691627678294L,
+                time = 1693425051905L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
                 inputSignatures = "private final  int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate  int mStartRotation\nprivate  int mEndRotation\nprivate  boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -283,6 +300,9 @@
      * @param triggerTask
      *   If non-null, If non-null, the task containing the activity whose lifecycle change (start or
      *   finish) has caused this transition to occur.
+     * @param pipTask
+     *   If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+     *   finish) has caused this transition to occur.
      * @param remoteTransition
      *   If non-null, a remote-transition associated with the source of this transition.
      * @param displayChange
@@ -296,6 +316,7 @@
     public TransitionRequestInfo(
             @WindowManager.TransitionType int type,
             @Nullable ActivityManager.RunningTaskInfo triggerTask,
+            @Nullable ActivityManager.RunningTaskInfo pipTask,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionRequestInfo.DisplayChange displayChange,
             int flags) {
@@ -303,6 +324,7 @@
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
+        this.mPipTask = pipTask;
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
@@ -319,7 +341,7 @@
     }
 
     /**
-     * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+     * If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
     @DataClass.Generated.Member
@@ -328,6 +350,15 @@
     }
 
     /**
+     * If non-null, the task containing the pip activity that participates in this
+     * transition.
+     */
+    @DataClass.Generated.Member
+    public @Nullable ActivityManager.RunningTaskInfo getPipTask() {
+        return mPipTask;
+    }
+
+    /**
      * If non-null, a remote-transition associated with the source of this transition.
      */
     @DataClass.Generated.Member
@@ -354,7 +385,7 @@
     }
 
     /**
-     * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+     * If non-null, the task containing the activity whose lifecycle change (start or
      * finish) has caused this transition to occur.
      */
     @DataClass.Generated.Member
@@ -364,6 +395,16 @@
     }
 
     /**
+     * If non-null, the task containing the pip activity that participates in this
+     * transition.
+     */
+    @DataClass.Generated.Member
+    public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
+        mPipTask = value;
+        return this;
+    }
+
+    /**
      * If non-null, a remote-transition associated with the source of this transition.
      */
     @DataClass.Generated.Member
@@ -392,6 +433,7 @@
         return "TransitionRequestInfo { " +
                 "type = " + mType + ", " +
                 "triggerTask = " + mTriggerTask + ", " +
+                "pipTask = " + mPipTask + ", " +
                 "remoteTransition = " + mRemoteTransition + ", " +
                 "displayChange = " + mDisplayChange + ", " +
                 "flags = " + mFlags +
@@ -406,11 +448,13 @@
 
         byte flg = 0;
         if (mTriggerTask != null) flg |= 0x2;
-        if (mRemoteTransition != null) flg |= 0x4;
-        if (mDisplayChange != null) flg |= 0x8;
+        if (mPipTask != null) flg |= 0x4;
+        if (mRemoteTransition != null) flg |= 0x8;
+        if (mDisplayChange != null) flg |= 0x10;
         dest.writeByte(flg);
         dest.writeInt(mType);
         if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
+        if (mPipTask != null) dest.writeTypedObject(mPipTask, flags);
         if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
         if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
         dest.writeInt(mFlags);
@@ -430,14 +474,16 @@
         byte flg = in.readByte();
         int type = in.readInt();
         ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
-        RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
-        TransitionRequestInfo.DisplayChange displayChange = (flg & 0x8) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
+        ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+        RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
+        TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
         int flags = in.readInt();
 
         this.mType = type;
         com.android.internal.util.AnnotationValidations.validate(
                 WindowManager.TransitionType.class, null, mType);
         this.mTriggerTask = triggerTask;
+        this.mPipTask = pipTask;
         this.mRemoteTransition = remoteTransition;
         this.mDisplayChange = displayChange;
         this.mFlags = flags;
@@ -460,10 +506,10 @@
     };
 
     @DataClass.Generated(
-            time = 1691627678327L,
+            time = 1693425051928L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
-            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+            inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final  int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
deleted file mode 100644
index f1d981a..0000000
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.window.flags"
-
-flag {
-  name: "letterbox_background_wallpaper_flag"
-  namespace: "large_screen_experiences_app_compat"
-  description: "Whether the letterbox wallpaper style is enabled by default"
-  bug: "297195682"
-}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6d8512c..7e2c017 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3024,28 +3024,31 @@
         return shouldShowTabs()
                 && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
                         UserHandle.of(UserHandle.myUserId())).getCount() > 0
-                    || shouldShowContentPreviewWhenEmpty())
+                    || shouldShowStickyContentPreviewWhenEmpty())
                 && shouldShowContentPreview();
     }
 
     /**
-     * This method could be used to override the default behavior when we hide the preview area
-     * when the current tab doesn't have any items.
+     * This method could be used to override the default behavior when we hide the sticky preview
+     * area when the current tab doesn't have any items.
      *
-     * @return true if we want to show the content preview area even if the tab for the current
-     *         user is empty
+     * @return {@code true} if we want to show the sticky content preview area even if the tab for
+     *         the current user is empty
      */
-    protected boolean shouldShowContentPreviewWhenEmpty() {
+    protected boolean shouldShowStickyContentPreviewWhenEmpty() {
         return false;
     }
 
-    /**
-     * @return true if we want to show the content preview area
-     */
-    protected boolean shouldShowContentPreview() {
+    @Override
+    public boolean shouldShowContentPreview() {
         return isSendAction(getTargetIntent());
     }
 
+    @Override
+    public boolean shouldShowServiceTargets() {
+        return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic();
+    }
+
     private void updateStickyContentPreview() {
         if (shouldShowStickyContentPreviewNoOrientationCheck()) {
             // The sticky content preview is only shown when we show the work and personal tabs.
@@ -3407,11 +3410,7 @@
         // There can be at most one row in the listview, that is internally
         // a ViewGroup with 2 rows
         public int getServiceTargetRowCount() {
-            if (shouldShowContentPreview()
-                    && !ActivityManager.isLowRamDeviceStatic()) {
-                return 1;
-            }
-            return 0;
+            return shouldShowServiceTargets() ? 1 : 0;
         }
 
         public int getAzLabelRowCount() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index f77e718..b3e828d 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -19,7 +19,6 @@
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
 
-import android.app.ActivityManager;
 import android.app.prediction.AppPredictor;
 import android.content.ComponentName;
 import android.content.Context;
@@ -426,11 +425,9 @@
     }
 
     public int getServiceTargetCount() {
-        if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
-                && !ActivityManager.isLowRamDeviceStatic()) {
+        if (mChooserListCommunicator.shouldShowServiceTargets()) {
             return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets());
         }
-
         return 0;
     }
 
@@ -779,6 +776,10 @@
         void sendListViewUpdateMessage(UserHandle userHandle);
 
         boolean isSendAction(Intent targetIntent);
+
+        boolean shouldShowContentPreview();
+
+        boolean shouldShowServiceTargets();
     }
 
     /**
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 52ffc98..a513ca5 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IdRes;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -53,10 +54,13 @@
     private static final String TAG = "ResolverDrawerLayout";
     private MetricsLogger mMetricsLogger;
 
+
+
     /**
-     * Max width of the whole drawer layout
+     * Max width of the whole drawer layout and its res id
      */
-    private final int mMaxWidth;
+    private int mMaxWidthResId;
+    private int mMaxWidth;
 
     /**
      * Max total visible height of views not marked always-show when in the closed/initial state
@@ -152,6 +156,7 @@
 
         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ResolverDrawerLayout,
                 defStyleAttr, 0);
+        mMaxWidthResId = a.getResourceId(R.styleable.ResolverDrawerLayout_maxWidth, -1);
         mMaxWidth = a.getDimensionPixelSize(R.styleable.ResolverDrawerLayout_maxWidth, -1);
         mMaxCollapsedHeight = a.getDimensionPixelSize(
                 R.styleable.ResolverDrawerLayout_maxCollapsedHeight, 0);
@@ -1042,6 +1047,18 @@
         return mAlwaysShowHeight;
     }
 
+    /**
+     * Max width of the drawer needs to be updated after the configuration is changed.
+     * For example, foldables have different layout width when the device is folded and unfolded.
+     */
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mMaxWidthResId > 0) {
+            mMaxWidth = getResources().getDimensionPixelSize(mMaxWidthResId);
+        }
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final int width = getWidth();
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index fe95762..e0bcef6 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -565,6 +565,51 @@
     return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL);
 }
 
+static jlong android_os_Debug_getRssPid(JNIEnv* env, jobject clazz, jint pid,
+                                        jlongArray outMemtrack) {
+    jlong rss = 0;
+    jlong memtrack = 0;
+
+    struct graphics_memory_pss graphics_mem;
+    if (read_memtrack_memory(pid, &graphics_mem) == 0) {
+        rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
+    }
+
+    ::android::meminfo::ProcMemInfo proc_mem(pid);
+    uint64_t status_rss;
+    if (proc_mem.StatusVmRSS(&status_rss)) {
+        rss += status_rss;
+    } else {
+        return 0;
+    }
+
+    if (outMemtrack != NULL) {
+        int outLen = env->GetArrayLength(outMemtrack);
+        if (outLen >= 1) {
+            jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
+            if (outMemtrackArray != NULL) {
+                outMemtrackArray[0] = memtrack;
+                if (outLen >= 2) {
+                    outMemtrackArray[1] = graphics_mem.graphics;
+                }
+                if (outLen >= 3) {
+                    outMemtrackArray[2] = graphics_mem.gl;
+                }
+                if (outLen >= 4) {
+                    outMemtrackArray[3] = graphics_mem.other;
+                }
+            }
+            env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
+        }
+    }
+
+    return rss;
+}
+
+static jlong android_os_Debug_getRss(JNIEnv* env, jobject clazz) {
+    return android_os_Debug_getRssPid(env, clazz, getpid(), NULL);
+}
+
 // The 1:1 mapping of MEMINFO_* enums here must match with the constants from
 // Debug.java.
 enum {
@@ -974,62 +1019,43 @@
  */
 
 static const JNINativeMethod gMethods[] = {
-    { "getNativeHeapSize",      "()J",
-            (void*) android_os_Debug_getNativeHeapSize },
-    { "getNativeHeapAllocatedSize", "()J",
-            (void*) android_os_Debug_getNativeHeapAllocatedSize },
-    { "getNativeHeapFreeSize",  "()J",
-            (void*) android_os_Debug_getNativeHeapFreeSize },
-    { "getMemoryInfo",          "(Landroid/os/Debug$MemoryInfo;)V",
-            (void*) android_os_Debug_getDirtyPages },
-    { "getMemoryInfo",          "(ILandroid/os/Debug$MemoryInfo;)Z",
-            (void*) android_os_Debug_getDirtyPagesPid },
-    { "getPss",                 "()J",
-            (void*) android_os_Debug_getPss },
-    { "getPss",                 "(I[J[J)J",
-            (void*) android_os_Debug_getPssPid },
-    { "getMemInfo",             "([J)V",
-            (void*) android_os_Debug_getMemInfo },
-    { "dumpNativeHeap",         "(Ljava/io/FileDescriptor;)V",
-            (void*) android_os_Debug_dumpNativeHeap },
-    { "dumpNativeMallocInfo",   "(Ljava/io/FileDescriptor;)V",
-            (void*) android_os_Debug_dumpNativeMallocInfo },
-    { "getBinderSentTransactions", "()I",
-            (void*) android_os_Debug_getBinderSentTransactions },
-    { "getBinderReceivedTransactions", "()I",
-            (void*) android_os_getBinderReceivedTransactions },
-    { "getBinderLocalObjectCount", "()I",
-            (void*)android_os_Debug_getLocalObjectCount },
-    { "getBinderProxyObjectCount", "()I",
-            (void*)android_os_Debug_getProxyObjectCount },
-    { "getBinderDeathObjectCount", "()I",
-            (void*)android_os_Debug_getDeathObjectCount },
-    { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
-            (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
-    { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
-            (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
-    { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
-            (void*)android_os_Debug_getUnreachableMemory },
-    { "getZramFreeKb", "()J",
-            (void*)android_os_Debug_getFreeZramKb },
-    { "getIonHeapsSizeKb", "()J",
-            (void*)android_os_Debug_getIonHeapsSizeKb },
-    { "getDmabufTotalExportedKb", "()J",
-            (void*)android_os_Debug_getDmabufTotalExportedKb },
-    { "getGpuPrivateMemoryKb", "()J",
-            (void*)android_os_Debug_getGpuPrivateMemoryKb },
-    { "getDmabufHeapTotalExportedKb", "()J",
-            (void*)android_os_Debug_getDmabufHeapTotalExportedKb },
-    { "getIonPoolsSizeKb", "()J",
-            (void*)android_os_Debug_getIonPoolsSizeKb },
-    { "getDmabufMappedSizeKb", "()J",
-            (void*)android_os_Debug_getDmabufMappedSizeKb },
-    { "getDmabufHeapPoolsSizeKb", "()J",
-            (void*)android_os_Debug_getDmabufHeapPoolsSizeKb },
-    { "getGpuTotalUsageKb", "()J",
-            (void*)android_os_Debug_getGpuTotalUsageKb },
-    { "isVmapStack", "()Z",
-            (void*)android_os_Debug_isVmapStack },
+        {"getNativeHeapSize", "()J", (void*)android_os_Debug_getNativeHeapSize},
+        {"getNativeHeapAllocatedSize", "()J", (void*)android_os_Debug_getNativeHeapAllocatedSize},
+        {"getNativeHeapFreeSize", "()J", (void*)android_os_Debug_getNativeHeapFreeSize},
+        {"getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V",
+         (void*)android_os_Debug_getDirtyPages},
+        {"getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z",
+         (void*)android_os_Debug_getDirtyPagesPid},
+        {"getPss", "()J", (void*)android_os_Debug_getPss},
+        {"getPss", "(I[J[J)J", (void*)android_os_Debug_getPssPid},
+        {"getRss", "()J", (void*)android_os_Debug_getRss},
+        {"getRss", "(I[J)J", (void*)android_os_Debug_getRssPid},
+        {"getMemInfo", "([J)V", (void*)android_os_Debug_getMemInfo},
+        {"dumpNativeHeap", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Debug_dumpNativeHeap},
+        {"dumpNativeMallocInfo", "(Ljava/io/FileDescriptor;)V",
+         (void*)android_os_Debug_dumpNativeMallocInfo},
+        {"getBinderSentTransactions", "()I", (void*)android_os_Debug_getBinderSentTransactions},
+        {"getBinderReceivedTransactions", "()I", (void*)android_os_getBinderReceivedTransactions},
+        {"getBinderLocalObjectCount", "()I", (void*)android_os_Debug_getLocalObjectCount},
+        {"getBinderProxyObjectCount", "()I", (void*)android_os_Debug_getProxyObjectCount},
+        {"getBinderDeathObjectCount", "()I", (void*)android_os_Debug_getDeathObjectCount},
+        {"dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
+         (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout},
+        {"dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
+         (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout},
+        {"getUnreachableMemory", "(IZ)Ljava/lang/String;",
+         (void*)android_os_Debug_getUnreachableMemory},
+        {"getZramFreeKb", "()J", (void*)android_os_Debug_getFreeZramKb},
+        {"getIonHeapsSizeKb", "()J", (void*)android_os_Debug_getIonHeapsSizeKb},
+        {"getDmabufTotalExportedKb", "()J", (void*)android_os_Debug_getDmabufTotalExportedKb},
+        {"getGpuPrivateMemoryKb", "()J", (void*)android_os_Debug_getGpuPrivateMemoryKb},
+        {"getDmabufHeapTotalExportedKb", "()J",
+         (void*)android_os_Debug_getDmabufHeapTotalExportedKb},
+        {"getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb},
+        {"getDmabufMappedSizeKb", "()J", (void*)android_os_Debug_getDmabufMappedSizeKb},
+        {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb},
+        {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
+        {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
 };
 
 int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index ddaeb5a..7f69e22 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -240,6 +240,36 @@
     return static_cast<jboolean>(*map1 == *map2);
 }
 
+static void nativeApplyOverlay(JNIEnv* env, jobject clazz, jlong ptr, jstring nameObj,
+        jstring overlayObj) {
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    if (!map || !map->getMap()) {
+        return;
+    }
+    ScopedUtfChars nameChars(env, nameObj);
+    ScopedUtfChars overlayChars(env, overlayObj);
+    base::Result<std::shared_ptr<KeyCharacterMap>> ret =
+            KeyCharacterMap::loadContents(nameChars.c_str(), overlayChars.c_str(),
+                                          KeyCharacterMap::Format::OVERLAY);
+    if (ret.ok()) {
+        std::shared_ptr<KeyCharacterMap> overlay = *ret;
+        map->getMap()->combine(*overlay);
+    }
+}
+
+static jint nativeGetMappedKey(JNIEnv* env, jobject clazz, jlong ptr, jint scanCode) {
+    NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+    if (!map || !map->getMap()) {
+        return 0;
+    }
+    int32_t outKeyCode;
+    status_t mapKeyRes = map->getMap()->mapKey(scanCode, /*usageCode=*/0, &outKeyCode);
+    if (mapKeyRes != OK) {
+        return 0;
+    }
+    return static_cast<jint>(outKeyCode);
+}
+
 /*
  * JNI registration.
  */
@@ -260,7 +290,9 @@
         {"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;",
          (void*)nativeObtainEmptyKeyCharacterMap},
         {"nativeEquals", "(JJ)Z", (void*)nativeEquals},
-};
+        {"nativeApplyOverlay", "(JLjava/lang/String;Ljava/lang/String;)V",
+         (void*)nativeApplyOverlay},
+        {"nativeGetMappedKey", "(JI)I", (void*)nativeGetMappedKey}};
 
 int register_android_view_KeyCharacterMap(JNIEnv* env)
 {
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index bdf7eaa..beb8c9b 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -50,13 +50,6 @@
 } gCaptureArgsClassInfo;
 
 static struct {
-    jfieldID displayToken;
-    jfieldID width;
-    jfieldID height;
-    jfieldID useIdentityTransform;
-} gDisplayCaptureArgsClassInfo;
-
-static struct {
     jfieldID layer;
     jfieldID childrenOnly;
 } gLayerCaptureArgsClassInfo;
@@ -181,39 +174,6 @@
                                  gCaptureArgsClassInfo.hintForSeamlessTransition);
 }
 
-static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
-                                                       jobject displayCaptureArgsObject) {
-    DisplayCaptureArgs captureArgs;
-    getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
-
-    captureArgs.displayToken =
-            ibinderForJavaObject(env,
-                                 env->GetObjectField(displayCaptureArgsObject,
-                                                     gDisplayCaptureArgsClassInfo.displayToken));
-    captureArgs.width =
-            env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
-    captureArgs.height =
-            env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
-    captureArgs.useIdentityTransform =
-            env->GetBooleanField(displayCaptureArgsObject,
-                                 gDisplayCaptureArgsClassInfo.useIdentityTransform);
-    return captureArgs;
-}
-
-static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
-                                 jlong screenCaptureListenerObject) {
-    const DisplayCaptureArgs captureArgs =
-            displayCaptureArgsFromObject(env, displayCaptureArgsObject);
-
-    if (captureArgs.displayToken == nullptr) {
-        return BAD_VALUE;
-    }
-
-    sp<gui::IScreenCaptureListener> captureListener =
-            reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
-    return ScreenshotClient::captureDisplay(captureArgs, captureListener);
-}
-
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
                                 jlong screenCaptureListenerObject) {
     LayerCaptureArgs captureArgs;
@@ -283,8 +243,6 @@
 
 static const JNINativeMethod sScreenCaptureMethods[] = {
         // clang-format off
-    {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
-            (void*)nativeCaptureDisplay },
     {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
             (void*)nativeCaptureLayers },
     {"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
@@ -317,17 +275,6 @@
     gCaptureArgsClassInfo.hintForSeamlessTransition =
             GetFieldIDOrDie(env, captureArgsClazz, "mHintForSeamlessTransition", "Z");
 
-    jclass displayCaptureArgsClazz =
-            FindClassOrDie(env, "android/window/ScreenCapture$DisplayCaptureArgs");
-    gDisplayCaptureArgsClassInfo.displayToken =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mDisplayToken", "Landroid/os/IBinder;");
-    gDisplayCaptureArgsClassInfo.width =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mWidth", "I");
-    gDisplayCaptureArgsClassInfo.height =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mHeight", "I");
-    gDisplayCaptureArgsClassInfo.useIdentityTransform =
-            GetFieldIDOrDie(env, displayCaptureArgsClazz, "mUseIdentityTransform", "Z");
-
     jclass layerCaptureArgsClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$LayerCaptureArgs");
     gLayerCaptureArgsClassInfo.layer =
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index 279a5d0..b9905e8 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -30,6 +30,7 @@
     optional string non_localized_label = 4;
     optional int32 icon = 5;
     optional int32 banner = 6;
+    optional bool is_archived = 7;
 }
 
 // Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3180ffb..c09f0a3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1699,6 +1699,15 @@
         android:description="@string/permdesc_cameraOpenCloseListener"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows camera access by Headless System User 0 when device is running in
+            HSUM Mode.
+    @hide -->
+    <permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_cameraHeadlessSystemUser"
+        android:description="@string/permdesc_cameraHeadlessSystemUser"
+        android:protectionLevel="signature" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device sensors                           -->
     <!-- ====================================================================== -->
@@ -7654,6 +7663,10 @@
     <permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"
                 android:protectionLevel="signature" />
 
+    <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities -->
+    <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows the holder to call health connect migration APIs.
         @hide -->
     <permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 591e505..fac6aac 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1444,6 +1444,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
     <string name="permdesc_cameraOpenCloseListener">This app can receive callbacks when any camera device is being opened (by what application) or closed.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permlab_cameraHeadlessSystemUser">Allow an application or service to access camera as Headless System User.</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+    <string name="permdesc_cameraHeadlessSystemUser">This app can access camera as Headless System User.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_vibrate">control vibration</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 7f56eb7..9cde296 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -61,11 +61,11 @@
         "testng",
         "servicestests-utils",
         "device-time-shell-utils",
+        "testables",
     ],
 
     libs: [
         "android.test.runner",
-        "testables",
         "org.apache.http.legacy",
         "android.test.base",
         "android.test.mock",
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 5dbeac2..407c6c3 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -26,6 +26,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class BroadcastReceiverTests {
@@ -47,15 +50,22 @@
     @Test
     public void testReceiverLimit() {
         final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction");
+        final List<EmptyReceiver> receivers = new ArrayList<>(RECEIVER_LIMIT_PER_APP);
         try {
             for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) {
-                mContext.registerReceiver(new EmptyReceiver(), mockFilter,
+                final EmptyReceiver receiver = new EmptyReceiver();
+                mContext.registerReceiver(receiver, mockFilter,
                         Context.RECEIVER_EXPORTED_UNAUDITED);
+                receivers.add(receiver);
             }
             fail("No exception thrown when registering "
                     + (RECEIVER_LIMIT_PER_APP + 1) + " receivers");
         } catch (IllegalStateException ise) {
             // Expected
+        } finally {
+            for (int i = receivers.size() - 1; i >= 0; i--) {
+                mContext.unregisterReceiver(receivers.remove(i));
+            }
         }
     }
 }
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 5553902..37ef6cb 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -85,9 +85,11 @@
         assertEquals(2, cache.getAllServicesSize(U0));
         assertEquals(2, cache.getPersistentServicesSize(U0));
         assertNotEmptyFileCreated(cache, U0);
+        cache.unregisterReceivers();
         // Make sure all services can be loaded from xml
         cache = new TestServicesCache();
         assertEquals(2, cache.getPersistentServicesSize(U0));
+        cache.unregisterReceivers();
     }
 
     public void testGetAllServicesReplaceUid() {
@@ -110,6 +112,7 @@
         assertTrue("UID must be updated to the new value",
                 uids.contains(SYSTEM_IMAGE_UID));
         assertFalse("UID must be updated to the new value", uids.contains(UID2));
+        cache.unregisterReceivers();
     }
 
     public void testGetAllServicesServiceRemoved() {
@@ -118,6 +121,7 @@
         cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
         assertEquals(2, cache.getAllServicesSize(U0));
         assertEquals(2, cache.getPersistentServicesSize(U0));
+        cache.unregisterReceivers();
         // Re-read data from disk and verify services were saved
         cache = new TestServicesCache();
         assertEquals(2, cache.getPersistentServicesSize(U0));
@@ -125,6 +129,7 @@
         cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
         assertEquals(1, cache.getAllServicesSize(U0));
         assertEquals(1, cache.getPersistentServicesSize(U0));
+        cache.unregisterReceivers();
     }
 
     public void testGetAllServicesMultiUser() {
@@ -137,12 +142,14 @@
         assertEquals(1, cache.getAllServicesSize(U1));
         assertEquals(1, cache.getPersistentServicesSize(U1));
         assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3));
+        cache.unregisterReceivers();
         // Re-read data from disk and verify services were saved
         cache = new TestServicesCache();
         assertEquals(1, cache.getPersistentServicesSize(U0));
         assertEquals(1, cache.getPersistentServicesSize(U1));
         assertNotEmptyFileCreated(cache, U0);
         assertNotEmptyFileCreated(cache, U1);
+        cache.unregisterReceivers();
     }
 
     public void testOnRemove() {
@@ -158,6 +165,7 @@
         cache.clearServicesForQuerying();
         assertEquals(1, cache.getAllServicesSize(U0));
         assertEquals(0, cache.getAllServicesSize(U1));
+        cache.unregisterReceivers();
     }
 
     public void testMigration() {
@@ -186,10 +194,12 @@
         cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2));
         assertEquals(2, cache.getAllServicesSize(u0));
         assertEquals(0, cache.getAllServicesSize(u1));
+        cache.unregisterReceivers();
         // Re-read data from disk. Verify that services were saved and old file was ignored
         cache = new TestServicesCache();
         assertEquals(2, cache.getPersistentServicesSize(u0));
         assertEquals(0, cache.getPersistentServicesSize(u1));
+        cache.unregisterReceivers();
     }
 
     private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 9840e15..7c4136d 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -35,6 +35,7 @@
 
 import org.junit.After;
 import org.junit.Assert;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,6 +72,7 @@
         deleteViaContentProvider(NAMESPACE, KEY2);
         deleteViaContentProvider(NAMESPACE, KEY3);
         DeviceConfig.clearAllLocalOverrides();
+        DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
     }
 
     /**
@@ -880,6 +882,7 @@
     }
 
     @Test
+    @Ignore("b/300174188")
     public void syncDisabling() throws Exception {
         Properties properties1 = new Properties.Builder(NAMESPACE)
                 .setString(KEY, VALUE)
diff --git a/core/tests/coretests/src/android/service/TEST_MAPPING b/core/tests/coretests/src/android/service/TEST_MAPPING
index fbf8a92..7ebda00 100644
--- a/core/tests/coretests/src/android/service/TEST_MAPPING
+++ b/core/tests/coretests/src/android/service/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksCoreTests",
       "options": [
diff --git a/core/tests/coretests/src/android/service/quicksettings/OWNERS b/core/tests/coretests/src/android/service/quicksettings/OWNERS
new file mode 100644
index 0000000..5665490
--- /dev/null
+++ b/core/tests/coretests/src/android/service/quicksettings/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/service/quicksettings/OWNERS
diff --git a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
index d28eeff..04af5d7 100644
--- a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
+++ b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
@@ -28,10 +28,10 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,13 +40,15 @@
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class TileServiceTest {
 
     @Mock
     private IQSService.Stub mIQSService;
 
+    private TestableLooper mTestableLooper;
+
     private IBinder mTileToken;
     private TileService mTileService;
     private Tile mTile;
@@ -55,6 +57,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
+        mTestableLooper = TestableLooper.get(this);
+
         mTileToken = new Binder();
         when(mIQSService.asBinder()).thenCallRealMethod();
         when(mIQSService.queryLocalInterface(anyString())).thenReturn(mIQSService);
@@ -72,6 +76,7 @@
         when(mIQSService.getTile(mTileToken)).thenThrow(new RemoteException());
 
         IBinder result = mTileService.onBind(intent);
+        mTestableLooper.processAllMessages();
         assertNull(result);
     }
 
@@ -83,6 +88,7 @@
         when(mIQSService.getTile(mTileToken)).thenReturn(null);
 
         IBinder result = mTileService.onBind(intent);
+        mTestableLooper.processAllMessages();
 
         assertNotNull(result);
         verify(mIQSService, never()).onStartSuccessful(any());
@@ -96,6 +102,7 @@
         when(mIQSService.getTile(mTileToken)).thenReturn(mTile);
 
         IBinder result = mTileService.onBind(intent);
+        mTestableLooper.processAllMessages();
 
         assertNotNull(result);
         verify(mIQSService).onStartSuccessful(mTileToken);
diff --git a/core/tests/coretests/src/android/util/PatternsTest.java b/core/tests/coretests/src/android/util/PatternsTest.java
index 6cea2f3..dd8f73f 100644
--- a/core/tests/coretests/src/android/util/PatternsTest.java
+++ b/core/tests/coretests/src/android/util/PatternsTest.java
@@ -399,7 +399,7 @@
     @SmallTest
     public void testAutoLinkWebUrl_doesNotMatchUrlsWithoutProtocolAndWithUnknownTld()
             throws Exception {
-        String url = "thank.you";
+        String url = "thank.unknowntld";
         assertFalse("Should not match URL that does not start with a protocol and " +
                 "does not contain a known TLD",
                 Patterns.AUTOLINK_WEB_URL.matcher(url).matches());
@@ -422,7 +422,7 @@
     @SmallTest
     public void testAutoLinkWebUrl_doesNotMatchUrlsWithEmojiWithoutProtocolAndWithoutKnownTld()
             throws Exception {
-        String url = "Thank\u263A.you";
+        String url = "Thank\u263A.unknowntld";
         assertFalse("Should not match URLs containing emoji and with unknown TLD",
                 Patterns.AUTOLINK_WEB_URL.matcher(url).matches());
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
index 2eeaf53..1c9f04a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -19,8 +19,12 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Icon
 import android.os.Bundle
 import android.os.UserHandle
 import android.service.chooser.ChooserTarget
@@ -32,12 +36,15 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.internal.R
 import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask
+import com.android.internal.app.chooser.DisplayResolveInfo
 import com.android.internal.app.chooser.SelectableTargetInfo
 import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
 import com.android.internal.app.chooser.TargetInfo
 import com.android.server.testutils.any
 import com.android.server.testutils.mock
 import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.anyInt
@@ -46,22 +53,25 @@
 
 @RunWith(AndroidJUnit4::class)
 class ChooserListAdapterTest {
-    private val packageManager = mock<PackageManager> {
-        whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
-    }
+    private val packageManager =
+        mock<PackageManager> { whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) }
     private val context = InstrumentationRegistry.getInstrumentation().getContext()
     private val resolverListController = mock<ResolverListController>()
-    private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
-        whenever(maxRankedTargets).thenReturn(0)
-    }
-    private val selectableTargetInfoCommunicator =
-        mock<SelectableTargetInfoCommunicator> {
-            whenever(targetIntent).thenReturn(mock())
+    private val chooserListCommunicator =
+        mock<ChooserListAdapter.ChooserListCommunicator> {
+            whenever(maxRankedTargets).thenReturn(0)
         }
+    private val selectableTargetInfoCommunicator =
+        mock<SelectableTargetInfoCommunicator> { whenever(targetIntent).thenReturn(mock()) }
     private val chooserActivityLogger = mock<ChooserActivityLogger>()
 
+    @Before
+    fun setUp() {
+        whenever(resolverListController.userHandle).thenReturn(UserHandle.CURRENT)
+    }
+
     private fun createChooserListAdapter(
-        taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+        taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask = createTaskProvider()
     ) =
         ChooserListAdapterOverride(
             context,
@@ -98,9 +108,8 @@
         view.tag = viewHolderOne
         val targetInfo = createSelectableTargetInfo()
         val iconTaskOne = mock<LoadDirectShareIconTask>()
-        val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
-            whenever(invoke()).thenReturn(iconTaskOne)
-        }
+        val testTaskProvider =
+            mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) }
         val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
         testSubject.testViewBind(view, targetInfo, 0)
 
@@ -114,6 +123,65 @@
         verify(testTaskProvider, times(1)).invoke()
     }
 
+    @Test
+    fun getServiceTargetCount_shouldNotShowServiceTargets_returnsZero() {
+        whenever(chooserListCommunicator.shouldShowServiceTargets()).thenReturn(false)
+        val adapter = createChooserListAdapter()
+        whenever(chooserListCommunicator.maxRankedTargets).thenReturn(10)
+        addServiceTargets(adapter, targetCount = 50)
+
+        assertThat(adapter.serviceTargetCount).isEqualTo(0)
+    }
+
+    private fun createTaskProvider(): (SelectableTargetInfo?) -> LoadDirectShareIconTask {
+        val iconTaskOne = mock<LoadDirectShareIconTask>()
+        val testTaskProvider =
+            mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) }
+        return { testTaskProvider.invoke() }
+    }
+
+    private fun addServiceTargets(adapter: ChooserListAdapter, targetCount: Int) {
+        val origTarget =
+            DisplayResolveInfo(
+                Intent(),
+                createResolveInfo(),
+                Intent(),
+                ResolverListAdapter.ResolveInfoPresentationGetter(context, 200, createResolveInfo())
+            )
+        val targets = mutableListOf<ChooserTarget>()
+        for (i in 1..targetCount) {
+            val score = 1f
+            val componentName = ComponentName("chooser.list.adapter", "Test$i")
+            val extras = Bundle()
+            val icon: Icon? = null
+            targets += ChooserTarget("Title $i", icon, score, componentName, extras)
+        }
+        val directShareToShortcutInfos = mapOf<ChooserTarget, ShortcutInfo>()
+        adapter.addServiceResults(
+            origTarget,
+            targets,
+            ChooserActivity.TARGET_TYPE_DEFAULT,
+            directShareToShortcutInfos
+        )
+    }
+
+    private fun createResolveInfo(): ResolveInfo {
+        val applicationInfo =
+            ApplicationInfo().apply {
+                packageName = "chooser.list.adapter"
+                name = "ChooserListAdapterTestApplication"
+            }
+        val activityInfo =
+            ActivityInfo().apply {
+                packageName = applicationInfo.packageName
+                name = "ChooserListAdapterTest"
+            }
+        activityInfo.applicationInfo = applicationInfo
+        val resolveInfo = ResolveInfo()
+        resolveInfo.activityInfo = activityInfo
+        return resolveInfo
+    }
+
     private fun createSelectableTargetInfo(): SelectableTargetInfo =
         SelectableTargetInfo(
             context,
@@ -125,13 +193,7 @@
         )
 
     private fun createChooserTarget(): ChooserTarget =
-        ChooserTarget(
-            "Title",
-            null,
-            1f,
-            ComponentName("package", "package.Class"),
-            Bundle()
-        )
+        ChooserTarget("Title", null, 1f, ComponentName("package", "package.Class"), Bundle())
 
     private fun createView(): View {
         val view = FrameLayout(context)
@@ -164,23 +226,23 @@
     chooserActivityLogger: ChooserActivityLogger?,
     initialIntentsUserHandle: UserHandle?,
     private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
-) : ChooserListAdapter(
-    context,
-    payloadIntents,
-    initialIntents,
-    rList,
-    filterLastUsed,
-    resolverListController,
-    chooserListCommunicator,
-    selectableTargetInfoCommunicator,
-    packageManager,
-    chooserActivityLogger,
-    initialIntentsUserHandle,
-) {
+) :
+    ChooserListAdapter(
+        context,
+        payloadIntents,
+        initialIntents,
+        rList,
+        filterLastUsed,
+        resolverListController,
+        chooserListCommunicator,
+        selectableTargetInfoCommunicator,
+        packageManager,
+        chooserActivityLogger,
+        initialIntentsUserHandle,
+    ) {
     override fun createLoadDirectShareIconTask(
         info: SelectableTargetInfo?
-    ): LoadDirectShareIconTask =
-        taskProvider.invoke(info)
+    ): LoadDirectShareIconTask = taskProvider.invoke(info)
 
     fun testViewBind(view: View?, info: TargetInfo?, position: Int) {
         onBindView(view, info, position)
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
index e870d60..8d825e4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
@@ -99,6 +99,7 @@
                     if (isCharging(intent) == mExpectedChargingState) {
                         mReady.open();
                     }
+                    context.unregisterReceiver(this);
                 }
             }, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         }
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 3206dd2..69aa401 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -235,6 +235,7 @@
 
     <privapp-permissions package="com.android.shell">
         <!-- Needed for test only -->
+        <permission name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"/>
         <permission name="android.permission.MANAGE_HEALTH_DATA"/>
         <permission name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"/>
         <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ee2eacf..28a4b49 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -253,12 +253,6 @@
       "group": "WM_DEBUG_BOOT",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-1883484959": {
-      "message": "Content Recording: Display %d state is now (%d), so update recording?",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/DisplayContent.java"
-    },
     "-1872288685": {
       "message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
       "level": "VERBOSE",
@@ -445,6 +439,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
     },
+    "-1700778361": {
+      "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1699018375": {
       "message": "Adding activity %s to task %s callers: %s",
       "level": "INFO",
@@ -649,12 +649,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
-    "-1480264178": {
-      "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "-1478175541": {
       "message": "No longer animating wallpaper targets!",
       "level": "VERBOSE",
@@ -1855,12 +1849,6 @@
       "group": "WM_DEBUG_ADD_REMOVE",
       "at": "com\/android\/server\/wm\/Task.java"
     },
-    "-452750194": {
-      "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "-451552570": {
       "message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.",
       "level": "DEBUG",
@@ -2377,6 +2365,12 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
+    "34106798": {
+      "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "34682671": {
       "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
       "level": "INFO",
@@ -2413,6 +2407,12 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
+    "61363198": {
+      "message": "Auto-PIP allowed, requesting PIP mode via requestStartTransition(): %s, willAutoPip: %b",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "74885950": {
       "message": "Waiting for top state to be released by %s",
       "level": "VERBOSE",
@@ -2425,12 +2425,6 @@
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
     },
-    "90764070": {
-      "message": "Could not report token removal to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowContextListenerController.java"
-    },
     "95216706": {
       "message": "hideIme target: %s ",
       "level": "DEBUG",
@@ -3079,12 +3073,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "643263584": {
-      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "644675193": {
       "message": "Real start recents",
       "level": "DEBUG",
@@ -4105,6 +4093,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "1687944543": {
+      "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s, since the surface is not available.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "1699269281": {
       "message": "Don't organize or trigger events for untrusted displayId=%d",
       "level": "WARN",
@@ -4333,6 +4327,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "1936800105": {
+      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "1945495497": {
       "message": "Focused window didn't have a valid surface drawn.",
       "level": "DEBUG",
@@ -4351,12 +4351,6 @@
       "group": "WM_DEBUG_TASKS",
       "at": "com\/android\/server\/wm\/RootWindowContainer.java"
     },
-    "1948483534": {
-      "message": "Could not report config changes to the window token client.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowContextListenerController.java"
-    },
     "1964565370": {
       "message": "Starting remote animation",
       "level": "INFO",
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 919a93b..0f3488b 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.maintenance.IKeystoreMaintenance;
 import android.system.keystore2.Domain;
 import android.system.keystore2.KeyDescriptor;
@@ -51,6 +52,7 @@
      * @hide
      */
     public static int onUserAdded(@NonNull int userId) {
+        StrictMode.noteDiskWrite();
         try {
             getService().onUserAdded(userId);
             return 0;
@@ -71,6 +73,7 @@
      * @hide
      */
     public static int onUserRemoved(int userId) {
+        StrictMode.noteDiskWrite();
         try {
             getService().onUserRemoved(userId);
             return 0;
@@ -93,6 +96,7 @@
      * @hide
      */
     public static int onUserPasswordChanged(int userId, @Nullable byte[] password) {
+        StrictMode.noteDiskWrite();
         try {
             getService().onUserPasswordChanged(userId, password);
             return 0;
@@ -110,6 +114,7 @@
      * be cleared.
      */
     public static int clearNamespace(@Domain int domain, long namespace) {
+        StrictMode.noteDiskWrite();
         try {
             getService().clearNamespace(domain, namespace);
             return 0;
@@ -129,6 +134,7 @@
      * @return UserState enum variant as integer if successful or an error
      */
     public static int getState(int userId) {
+        StrictMode.noteDiskRead();
         try {
             return getService().getState(userId);
         } catch (ServiceSpecificException e) {
@@ -144,6 +150,7 @@
      * Informs Keystore 2.0 that an off body event was detected.
      */
     public static void onDeviceOffBody() {
+        StrictMode.noteDiskWrite();
         try {
             getService().onDeviceOffBody();
         } catch (Exception e) {
@@ -172,6 +179,7 @@
      *         * SYSTEM_ERROR if an unexpected error occurred.
      */
     public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
+        StrictMode.noteDiskWrite();
         try {
             getService().migrateKeyNamespace(source, destination);
             return 0;
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 00219e7..2d2dd24 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -22,6 +22,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.authorization.IKeystoreAuthorization;
 import android.security.authorization.LockScreenEvent;
 import android.system.keystore2.ResponseCode;
@@ -48,6 +49,7 @@
      * @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
      */
     public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
+        StrictMode.noteSlowCall("addAuthToken");
         try {
             getService().addAuthToken(authToken);
             return 0;
@@ -81,6 +83,7 @@
      */
     public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId,
             @Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) {
+        StrictMode.noteDiskWrite();
         try {
             if (locked) {
                 getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8811a7f..8045f55 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -18,6 +18,7 @@
 
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.StrictMode;
 import android.os.UserHandle;
 import android.security.maintenance.UserState;
 
@@ -126,6 +127,8 @@
      * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
      */
     public int addAuthToken(byte[] authToken) {
+        StrictMode.noteDiskWrite();
+
         return Authorization.addAuthToken(authToken);
     }
 
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index c83f298..5e16bce 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -23,6 +23,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.keymaster.KeymasterDefs;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreService;
@@ -148,6 +149,8 @@
     }
 
     void delete(KeyDescriptor descriptor) throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         handleRemoteExceptionWithRetry((service) -> {
             service.deleteKey(descriptor);
             return 0;
@@ -158,6 +161,8 @@
      * List all entries in the keystore for in the given namespace.
      */
     public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException {
+        StrictMode.noteDiskRead();
+
         return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
     }
 
@@ -166,6 +171,8 @@
      */
     public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias)
             throws KeyStoreException {
+        StrictMode.noteDiskRead();
+
         return handleRemoteExceptionWithRetry(
                 (service) -> service.listEntriesBatched(domain, namespace, startPastAlias));
     }
@@ -228,6 +235,8 @@
      */
     public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector)
             throws  KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         return handleRemoteExceptionWithRetry(
                 (service) -> service.grant(descriptor, granteeUid, accessVector)
         );
@@ -243,6 +252,8 @@
      */
     public void ungrant(KeyDescriptor descriptor, int granteeUid)
             throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         handleRemoteExceptionWithRetry((service) -> {
             service.ungrant(descriptor, granteeUid);
             return 0;
@@ -259,6 +270,8 @@
      */
     public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)
             throws KeyStoreException {
+        StrictMode.noteDiskRead();
+
         return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
     }
 
@@ -290,6 +303,8 @@
      */
     public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert,
             byte[] publicCertChain) throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         handleRemoteExceptionWithRetry((service) -> {
             service.updateSubcomponent(key, publicCert, publicCertChain);
             return 0;
@@ -305,6 +320,8 @@
      */
     public void deleteKey(@NonNull KeyDescriptor descriptor)
             throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         handleRemoteExceptionWithRetry((service) -> {
             service.deleteKey(descriptor);
             return 0;
@@ -315,6 +332,8 @@
      * Returns the number of Keystore entries for a given domain and namespace.
      */
     public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException {
+        StrictMode.noteDiskRead();
+
         return handleRemoteExceptionWithRetry((service)
                 -> service.getNumberOfEntries(domain, namespace));
     }
diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java
index 737ff2b..7c9b8eb 100644
--- a/keystore/java/android/security/KeyStoreOperation.java
+++ b/keystore/java/android/security/KeyStoreOperation.java
@@ -21,6 +21,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.keymaster.KeymasterDefs;
 import android.system.keystore2.IKeystoreOperation;
 import android.system.keystore2.ResponseCode;
@@ -97,6 +98,7 @@
      * @throws KeyStoreException
      */
     public void updateAad(@NonNull byte[] input) throws KeyStoreException {
+        StrictMode.noteSlowCall("updateAad");
         handleExceptions(() -> {
             mOperation.updateAad(input);
             return 0;
@@ -112,6 +114,7 @@
      * @hide
      */
     public byte[] update(@NonNull byte[] input) throws KeyStoreException {
+        StrictMode.noteSlowCall("update");
         return handleExceptions(() -> mOperation.update(input));
     }
 
@@ -125,6 +128,7 @@
      * @hide
      */
     public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException {
+        StrictMode.noteSlowCall("finish");
         return handleExceptions(() -> mOperation.finish(input, signature));
     }
 
@@ -135,6 +139,7 @@
      * @hide
      */
     public void abort() throws KeyStoreException {
+        StrictMode.noteSlowCall("abort");
         handleExceptions(() -> {
             mOperation.abort();
             return 0;
diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
index 9c0b46c..6ab148a 100644
--- a/keystore/java/android/security/KeyStoreSecurityLevel.java
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -22,6 +22,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.keystore.BackendBusyException;
 import android.security.keystore.KeyStoreConnectException;
 import android.system.keystore2.AuthenticatorSpec;
@@ -75,6 +76,7 @@
      */
     public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor,
             Collection<KeyParameter> args) throws KeyStoreException {
+        StrictMode.noteDiskWrite();
         while (true) {
             try {
                 CreateOperationResponse createOperationResponse =
@@ -142,6 +144,8 @@
     public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
             Collection<KeyParameter> args, int flags, byte[] entropy)
             throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         return handleExceptions(() -> mSecurityLevel.generateKey(
                 descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
                 flags, entropy));
@@ -163,6 +167,8 @@
     public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
             Collection<KeyParameter> args, int flags, byte[] keyData)
             throws KeyStoreException {
+        StrictMode.noteDiskWrite();
+
         return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
                 args.toArray(new KeyParameter[args.size()]), flags, keyData));
     }
@@ -186,6 +192,7 @@
             @NonNull byte[] wrappedKey, byte[] maskingKey,
             Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)
             throws KeyStoreException {
+        StrictMode.noteDiskWrite();
         KeyDescriptor keyDescriptor = new KeyDescriptor();
         keyDescriptor.alias = wrappedKeyDescriptor.alias;
         keyDescriptor.nspace = wrappedKeyDescriptor.nspace;
diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java
index c85b6b1..0cc4dfa 100644
--- a/keystore/java/android/security/LegacyVpnProfileStore.java
+++ b/keystore/java/android/security/LegacyVpnProfileStore.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
+import android.os.StrictMode;
 import android.security.legacykeystore.ILegacyKeystore;
 import android.util.Log;
 
@@ -51,6 +52,7 @@
      * @hide
      */
     public static boolean put(@NonNull String alias, @NonNull byte[] profile) {
+        StrictMode.noteDiskWrite();
         try {
             getService().put(alias, ILegacyKeystore.UID_SELF, profile);
             return true;
@@ -70,6 +72,7 @@
      * @hide
      */
     public static byte[] get(@NonNull String alias) {
+        StrictMode.noteDiskRead();
         try {
             return getService().get(alias, ILegacyKeystore.UID_SELF);
         } catch (ServiceSpecificException e) {
@@ -89,6 +92,7 @@
      * @hide
      */
     public static boolean remove(@NonNull String alias) {
+        StrictMode.noteDiskWrite();
         try {
             getService().remove(alias, ILegacyKeystore.UID_SELF);
             return true;
@@ -109,6 +113,7 @@
      * @hide
      */
     public static @NonNull String[] list(@NonNull String prefix) {
+        StrictMode.noteDiskRead();
         try {
             final String[] aliases = getService().list(prefix, ILegacyKeystore.UID_SELF);
             for (int i = 0; i < aliases.length; ++i) {
diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java
index e07eaa2..d481a07 100644
--- a/keystore/java/android/security/SystemKeyStore.java
+++ b/keystore/java/android/security/SystemKeyStore.java
@@ -18,6 +18,9 @@
 
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.StrictMode;
+
+import libcore.io.IoUtils;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -28,8 +31,6 @@
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
-import libcore.io.IoUtils;
-
 /**
  *@hide
  */
@@ -69,6 +70,7 @@
 
     public byte[] generateNewKey(int numBits, String algName, String keyName)
             throws NoSuchAlgorithmException {
+        StrictMode.noteDiskWrite();
 
         // Check if key with similar name exists. If so, return null.
         File keyFile = getKeyFile(keyName);
@@ -103,6 +105,7 @@
     }
 
     private File getKeyFile(String keyName) {
+        StrictMode.noteDiskWrite();
         File sysKeystoreDir = new File(Environment.getDataDirectory(),
                 SYSTEM_KEYSTORE_DIRECTORY);
         File keyFile = new File(sysKeystoreDir, keyName + KEY_FILE_EXTENSION);
@@ -114,6 +117,7 @@
     }
 
     public byte[] retrieveKey(String keyName) throws IOException {
+        StrictMode.noteDiskRead();
         File keyFile = getKeyFile(keyName);
         if (!keyFile.exists()) {
             return null;
@@ -122,6 +126,7 @@
     }
 
     public void deleteKey(String keyName) {
+        StrictMode.noteDiskWrite();
 
         // Get the file first.
         File keyFile = getKeyFile(keyName);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index d129891..9ac0f6d 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.security.keymint.KeyParameter;
+import android.os.StrictMode;
 import android.security.KeyStoreException;
 import android.security.KeyStoreOperation;
 import android.security.keymaster.KeymasterDefs;
@@ -137,6 +138,7 @@
         if (!(key instanceof AndroidKeyStorePrivateKey)
                 && (key instanceof PrivateKey || key instanceof PublicKey)) {
             try {
+                StrictMode.noteSlowCall("engineInit");
                 mCipher = Cipher.getInstance(getTransform());
                 String transform = getTransform();
 
@@ -203,6 +205,7 @@
         if (!(key instanceof AndroidKeyStorePrivateKey)
                 && (key instanceof PrivateKey || key instanceof PublicKey)) {
             try {
+                StrictMode.noteSlowCall("engineInit");
                 mCipher = Cipher.getInstance(getTransform());
                 mCipher.init(opmode, key, params, random);
                 return;
@@ -233,6 +236,7 @@
         if (!(key instanceof AndroidKeyStorePrivateKey)
                 && (key instanceof PrivateKey || key instanceof PublicKey)) {
             try {
+                StrictMode.noteSlowCall("engineInit");
                 mCipher = Cipher.getInstance(getTransform());
                 mCipher.init(opmode, key, params, random);
                 return;
@@ -346,6 +350,7 @@
         parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
 
         try {
+            StrictMode.noteDiskRead();
             mOperation = mKey.getSecurityLevel().createOperation(
                     mKey.getKeyIdDescriptor(),
                     parameters
@@ -521,6 +526,7 @@
     @Override
     protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
         if (mCipher != null) {
+            StrictMode.noteSlowCall("engineUpdateAAD");
             mCipher.updateAAD(input, inputOffset, inputLen);
             return;
         }
@@ -562,6 +568,7 @@
     @Override
     protected final void engineUpdateAAD(ByteBuffer src) {
         if (mCipher != null) {
+            StrictMode.noteSlowCall("engineUpdateAAD");
             mCipher.updateAAD(src);
             return;
         }
@@ -715,6 +722,7 @@
             throw new NullPointerException("key == null");
         }
         byte[] encoded = null;
+        StrictMode.noteSlowCall("engineWrap");
         if (key instanceof SecretKey) {
             if ("RAW".equalsIgnoreCase(key.getFormat())) {
                 encoded = key.getEncoded();
@@ -807,6 +815,7 @@
             throw new InvalidKeyException("Failed to unwrap key", e);
         }
 
+        StrictMode.noteSlowCall("engineUnwrap");
         switch (wrappedKeyType) {
             case Cipher.SECRET_KEY:
             {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index ace2053..9d3fca8 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -195,7 +195,7 @@
     protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
         if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) {
             throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
-                    + ". Only" + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
+                    + ". Only " + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
                     + " supported");
         }
 
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
index 7292cd3..66e9f71 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -20,6 +20,7 @@
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.KeyPurpose;
 import android.hardware.security.keymint.Tag;
+import android.os.StrictMode;
 import android.security.KeyStoreException;
 import android.security.KeyStoreOperation;
 import android.security.keystore.KeyStoreCryptoOperation;
@@ -174,6 +175,7 @@
         }
         byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded();
 
+        StrictMode.noteSlowCall("engineGenerateSecret");
         try {
             return mOperation.finish(otherPartyKeyEncoded, null);
         } catch (KeyStoreException e) {
@@ -245,6 +247,7 @@
                 Tag.PURPOSE, KeyPurpose.AGREE_KEY
         ));
 
+        StrictMode.noteDiskWrite();
         try {
             mOperation =
                     mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
index f1681ec..d283b05 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -18,6 +18,7 @@
 
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
+import android.os.StrictMode;
 import android.security.KeyStore2;
 import android.security.KeyStoreSecurityLevel;
 import android.security.keymaster.KeymasterDefs;
@@ -281,6 +282,7 @@
 
     @Override
     protected SecretKey engineGenerateKey() {
+        StrictMode.noteSlowCall("engineGenerateKey");
         KeyGenParameterSpec spec = mSpec;
         if (spec == null) {
             throw new IllegalStateException("Not initialized");
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 474b7ea..1398da3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -27,6 +27,7 @@
 import android.hardware.security.keymint.SecurityLevel;
 import android.hardware.security.keymint.Tag;
 import android.os.Build;
+import android.os.StrictMode;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore2;
 import android.security.KeyStoreException;
@@ -617,6 +618,7 @@
 
     @Override
     public KeyPair generateKeyPair() {
+        StrictMode.noteSlowCall("generateKeyPair");
         if (mKeyStore == null || mSpec == null) {
             throw new IllegalStateException("Not initialized");
         }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
index 931c2f8..d5fb49a 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
@@ -189,7 +189,7 @@
     protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
         if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
             throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
-                    + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+                    + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
         }
         super.initKey(key);
     }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index b4d8def..273dff1 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -24,6 +24,7 @@
 import android.hardware.security.keymint.HardwareAuthenticatorType;
 import android.hardware.security.keymint.KeyParameter;
 import android.hardware.security.keymint.SecurityLevel;
+import android.os.StrictMode;
 import android.security.GateKeeper;
 import android.security.KeyStore2;
 import android.security.KeyStoreParameter;
@@ -164,6 +165,7 @@
         KeyDescriptor descriptor = makeKeyDescriptor(alias);
 
         try {
+            StrictMode.noteDiskRead();
             return mKeyStore.getKeyEntry(descriptor);
         } catch (android.security.KeyStoreException e) {
             if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
@@ -447,6 +449,7 @@
             assertCanReplace(alias, targetDomain, mNamespace, descriptor);
 
             try {
+                StrictMode.noteDiskWrite();
                 mKeyStore.updateSubcomponents(
                         ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(),
                         userCertBytes, chainBytes);
@@ -597,6 +600,7 @@
                     importArgs, flags, pkcs8EncodedPrivateKeyBytes);
 
             try {
+                StrictMode.noteDiskWrite();
                 mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes);
             } catch (android.security.KeyStoreException e) {
                 mKeyStore.deleteKey(metadata.key);
@@ -938,6 +942,7 @@
 
         KeyEntryResponse response = null;
         try {
+            StrictMode.noteDiskRead();
             response = mKeyStore.getKeyEntry(wrappingkey);
         } catch (android.security.KeyStoreException e) {
             throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
@@ -994,6 +999,7 @@
         }
 
         try {
+            StrictMode.noteDiskWrite();
             securityLevel.importWrappedKey(
                     wrappedKey, wrappingkey,
                     entry.getWrappedKeyBytes(),
@@ -1054,6 +1060,7 @@
         }
 
         try {
+            StrictMode.noteDiskWrite();
             mKeyStore.updateSubcomponents(makeKeyDescriptor(alias),
                     null /* publicCert - unused when used as pure certificate store. */,
                     encoded);
@@ -1066,6 +1073,7 @@
     public void engineDeleteEntry(String alias) throws KeyStoreException {
         KeyDescriptor descriptor = makeKeyDescriptor(alias);
         try {
+            StrictMode.noteDiskWrite();
             mKeyStore.deleteKey(descriptor);
         } catch (android.security.KeyStoreException e) {
             if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
@@ -1076,6 +1084,7 @@
 
     private KeyDescriptor[] getAliasesBatch(String startPastAlias) {
         try {
+            StrictMode.noteDiskRead();
             return mKeyStore.listBatch(
                     getTargetDomain(),
                     mNamespace,
@@ -1103,6 +1112,7 @@
     @Override
     public int engineSize() {
         try {
+            StrictMode.noteDiskRead();
             return mKeyStore.getNumberOfEntries(
                     getTargetDomain(),
                     mNamespace
@@ -1166,6 +1176,7 @@
 
         KeyDescriptor[] keyDescriptors = null;
         try {
+            StrictMode.noteDiskRead();
             keyDescriptors = mKeyStore.list(
                     getTargetDomain(),
                     mNamespace
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 0112e32..15d14e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -213,9 +213,6 @@
             if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
                 mRearDisplayStateRequest = null;
                 mDeviceStateManager.cancelStateRequest();
-            } else {
-                throw new IllegalStateException(
-                        "Unable to cancel a rear display session as there is no active session");
             }
         }
     }
@@ -432,10 +429,6 @@
         synchronized (mLock) {
             if (mRearDisplayPresentationController != null) {
                 mDeviceStateManager.cancelStateRequest();
-            } else {
-                throw new IllegalStateException(
-                        "Unable to cancel a rear display presentation session as there is no "
-                                + "active session");
             }
         }
     }
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 a5ee19e..cdfc4c8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -879,14 +879,12 @@
 
         // Skip resolving if the activity is on a pinned TaskFragmentContainer.
         // TODO(b/243518738): skip resolving for overlay container.
-        if (container != null) {
-            final TaskContainer taskContainer = container.getTaskContainer();
-            if (taskContainer.isTaskFragmentContainerPinned(container)) {
-                return true;
-            }
+        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+        if (container != null && taskContainer != null
+                && taskContainer.isTaskFragmentContainerPinned(container)) {
+            return true;
         }
 
-        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
         if (!isOnReparent && taskContainer != null
                 && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                         != container) {
@@ -895,6 +893,28 @@
             return true;
         }
 
+        // Ensure the top TaskFragments are updated to the right config if activity is resolved
+        // to a new TaskFragment while pin TF exists.
+        final boolean handled = resolveActivityToContainerByRule(wct, activity, container,
+                isOnReparent);
+        if (handled && taskContainer != null) {
+            final SplitPinContainer splitPinContainer = taskContainer.getSplitPinContainer();
+            if (splitPinContainer != null) {
+                final TaskFragmentContainer resolvedContainer = getContainerWithActivity(activity);
+                if (resolvedContainer != null && resolvedContainer.getRunningActivityCount() <= 1) {
+                    updateContainer(wct, splitPinContainer.getSecondaryContainer());
+                }
+            }
+        }
+        return handled;
+    }
+
+    /**
+     * Resolves the activity to a {@link TaskFragmentContainer} according to the Split-rules.
+     */
+    boolean resolveActivityToContainerByRule(@NonNull WindowContainerTransaction wct,
+            @NonNull Activity activity, @Nullable TaskFragmentContainer container,
+            boolean isOnReparent) {
         /*
          * We will check the following to see if there is any embedding rule matched:
          * 1. Whether the new launched activity should always expand.
@@ -1301,6 +1321,26 @@
             }
         }
 
+        // Ensure the top TaskFragments are updated to the right config if the intent is resolved
+        // to a new TaskFragment while pin TF exists.
+        final TaskFragmentContainer launchingContainer = resolveStartActivityIntentByRule(wct,
+                taskId, intent, launchingActivity);
+        if (launchingContainer != null && launchingContainer.getRunningActivityCount() == 0) {
+            final SplitPinContainer splitPinContainer =
+                    launchingContainer.getTaskContainer().getSplitPinContainer();
+            if (splitPinContainer != null) {
+                updateContainer(wct, splitPinContainer.getSecondaryContainer());
+            }
+        }
+        return launchingContainer;
+    }
+
+    /**
+     * Resolves the intent to a {@link TaskFragmentContainer} according to the Split-rules.
+     */
+    @Nullable
+    TaskFragmentContainer resolveStartActivityIntentByRule(@NonNull WindowContainerTransaction wct,
+            int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
         /*
          * We will check the following to see if there is any embedding rule matched:
          * 1. Whether the new activity intent should always expand.
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index 5f9dbdb..da8abde 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -44,6 +44,12 @@
     if a custom action is present before closing it. -->
     <integer name="config_pipForceCloseDelay">5000</integer>
 
+    <!-- Animation duration when exit starting window: fade out icon -->
+    <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer>
+
+    <!-- Animation delay when exit starting window: reveal app -->
+    <integer name="starting_window_app_reveal_anim_delay">0</integer>
+
     <!-- Animation duration when exit starting window: reveal app -->
     <integer name="starting_window_app_reveal_anim_duration">500</integer>
 
diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml
new file mode 100644
index 0000000..03736ed
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/config.xml
@@ -0,0 +1,35 @@
+<?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.
+  -->
+
+<!-- These resources are around just to allow their values to be customized
+     for watch products.  Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Animation duration when exit starting window: fade out icon -->
+    <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer>
+
+    <!-- Animation delay when exit starting window: reveal app -->
+    <integer name="starting_window_app_reveal_anim_delay">50</integer>
+
+    <!-- Animation duration when exit starting window: reveal app -->
+    <integer name="starting_window_app_reveal_anim_duration">200</integer>
+
+    <!-- Default animation type when hiding the starting window. The possible values are:
+          - 0 for radial vanish + slide up
+          - 1 for fade out -->
+    <integer name="starting_window_exit_animation_type">1</integer>
+</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9ce0118..f8dd208 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -61,6 +60,7 @@
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
 import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
@@ -213,6 +213,7 @@
             "DesktopTasksController: moveToDesktop taskId=%d",
             task.taskId
         )
+        exitSplitIfApplicable(wct, task)
         // Bring other apps to front first
         bringDesktopAppsToFront(task.displayId, wct)
         addMoveToDesktopChanges(wct, task)
@@ -240,6 +241,7 @@
             taskInfo.taskId
         )
         val wct = WindowContainerTransaction()
+        exitSplitIfApplicable(wct, taskInfo)
         moveHomeTaskToFront(wct)
         addMoveToDesktopChanges(wct, taskInfo)
         wct.setBounds(taskInfo.token, startBounds)
@@ -319,7 +321,7 @@
             task.taskId
         )
         val wct = WindowContainerTransaction()
-        wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+        wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW)
         wct.setBounds(task.token, Rect())
         wct.setDensityDpi(task.token, getDefaultDensityDpi())
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -329,6 +331,13 @@
         }
     }
 
+    private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
+        if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+            splitScreenController.prepareExitSplitScreen(wct,
+                splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP)
+        }
+    }
+
     /**
      * The second part of the animated move to desktop transition, called after
      * {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 069066e..2616b8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -34,4 +34,10 @@
     default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
             Consumer<List<GroupedRecentTaskInfo>> callback) {
     }
+
+    /**
+     * Adds the listener to be notified of whether the recent task animation is running.
+     */
+    default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) {
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 94e1b33..ccc3438 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -428,6 +428,21 @@
                 executor.execute(() -> callback.accept(tasks));
             });
         }
+
+        @Override
+        public void addAnimationStateListener(Executor executor, Consumer<Boolean> listener) {
+            mMainExecutor.execute(() -> {
+                if (mTransitionHandler == null) {
+                    return;
+                }
+                mTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
+                    @Override
+                    public void onAnimationStateChanged(boolean running) {
+                        executor.execute(() -> listener.accept(running));
+                    }
+                });
+            });
+        }
     }
 
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 016771d..7ae0666 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -76,6 +76,7 @@
     private final RecentTasksController mRecentTasksController;
     private IApplicationThread mAnimApp = null;
     private final ArrayList<RecentsController> mControllers = new ArrayList<>();
+    private final ArrayList<RecentsTransitionStateListener> mStateListeners = new ArrayList<>();
 
     /**
      * List of other handlers which might need to mix recents with other things. These are checked
@@ -106,6 +107,11 @@
         mMixers.remove(mixer);
     }
 
+    /** Adds the callback for receiving the state change of transition. */
+    public void addTransitionStateListener(RecentsTransitionStateListener listener) {
+        mStateListeners.add(listener);
+    }
+
     @VisibleForTesting
     public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options,
             IApplicationThread appThread, IRecentsAnimationRunner listener) {
@@ -226,6 +232,13 @@
         private ArrayList<TaskState> mPausingTasks = null;
 
         /**
+         * List of tasks were pausing but closed in a subsequent merged transition. If a
+         * closing task is reopened, the leash is not initially hidden since it is already
+         * visible.
+         */
+        private ArrayList<TaskState> mClosingTasks = null;
+
+        /**
          * List of tasks that we are switching to. Upon finish, these will remain visible and
          * on top.
          */
@@ -376,11 +389,15 @@
             }
             mFinishTransaction = null;
             mPausingTasks = null;
+            mClosingTasks = null;
             mOpeningTasks = null;
             mInfo = null;
             mTransition = null;
             mPendingPauseSnapshotsForCancel = null;
             mControllers.remove(this);
+            for (int i = 0; i < mStateListeners.size(); i++) {
+                mStateListeners.get(i).onAnimationStateChanged(false);
+            }
         }
 
         boolean start(TransitionInfo info, SurfaceControl.Transaction t,
@@ -420,6 +437,7 @@
             mFinishCB = finishCB;
             mFinishTransaction = finishT;
             mPausingTasks = new ArrayList<>();
+            mClosingTasks = new ArrayList<>();
             mOpeningTasks = new ArrayList<>();
             mLeashMap = new ArrayMap<>();
             mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
@@ -519,6 +537,9 @@
                         apps.toArray(new RemoteAnimationTarget[apps.size()]),
                         wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]),
                         new Rect(0, 0, 0, 0), new Rect(), b);
+                for (int i = 0; i < mStateListeners.size(); i++) {
+                    mStateListeners.get(i).onAnimationStateChanged(true);
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error starting recents animation", e);
                 cancel("onAnimationStart() failed");
@@ -572,7 +593,9 @@
             final TransitionUtil.LeafTaskFilter leafTaskFilter =
                     new TransitionUtil.LeafTaskFilter();
             boolean hasTaskChange = false;
-            for (int i = 0; i < info.getChanges().size(); ++i) {
+            // Walk backwards so that higher z-order changes are recorded *last* in the assorted
+            // task lists. This way, when the are added, the on-top tasks are drawn on top.
+            for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
                 if (taskInfo != null
@@ -671,7 +694,10 @@
                     final TransitionInfo.Change change = closingTasks.get(i);
                     final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
                     if (pausingIdx >= 0) {
-                        mPausingTasks.remove(pausingIdx);
+                        // We are closing the pausing task, but it is still visible and can be
+                        // restart by another transition prior to this transition finishing
+                        final TaskState closingTask = mPausingTasks.remove(pausingIdx);
+                        mClosingTasks.add(closingTask);
                         didMergeThings = true;
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
                                 "  closing pausing taskId=%d", change.getTaskInfo().taskId);
@@ -707,7 +733,12 @@
                 for (int i = 0; i < openingTasks.size(); ++i) {
                     final TransitionInfo.Change change = openingTasks.get(i);
                     final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
-                    int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+                    final int closingIdx = TaskState.indexOf(mClosingTasks, change);
+                    if (closingIdx >= 0) {
+                        // Remove opening tasks from closing set
+                        mClosingTasks.remove(closingIdx);
+                    }
+                    final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
                     if (pausingIdx >= 0) {
                         // Something is showing/opening a previously-pausing app.
                         if (isLeaf) {
@@ -730,12 +761,23 @@
                         appearedTargets[nextTargetIdx++] = target;
                         // reparent into the original `mInfo` since that's where we are animating.
                         final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+                        final boolean wasClosing = closingIdx >= 0;
                         t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
                         t.setLayer(target.leash, layer);
-                        // Hide the animation leash, let listener show it.
-                        t.hide(target.leash);
+                        if (wasClosing) {
+                            // App was previously visible and is closing
+                            t.show(target.leash);
+                            t.setAlpha(target.leash, 1f);
+                            // Also override the task alpha as it was set earlier when dispatching
+                            // the transition and setting up the leash to hide the
+                            t.setAlpha(change.getLeash(), 1f);
+                        } else {
+                            // Hide the animation leash, let the listener show it
+                            t.hide(target.leash);
+                        }
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                                "  opening new leaf taskId=%d", target.taskId);
+                                "  opening new leaf taskId=%d wasClosing=%b",
+                                target.taskId, wasClosing);
                         mOpeningTasks.add(new TaskState(change, target.leash));
                     } else {
                         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
new file mode 100644
index 0000000..804dcc8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionStateListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 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.recents;
+
+/** The listener for the events from {@link RecentsTransitionHandler}. */
+public interface RecentsTransitionStateListener {
+
+    /** Notifies whether the recents animation is running. */
+    default void onAnimationStateChanged(boolean running) {
+    }
+}
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 991b699..f70b0fc5 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
@@ -23,7 +23,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -131,6 +130,7 @@
     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
     public static final int EXIT_REASON_RECREATE_SPLIT = 10;
     public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
+    public static final int EXIT_REASON_ENTER_DESKTOP = 12;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -144,6 +144,7 @@
             EXIT_REASON_CHILD_TASK_ENTER_PIP,
             EXIT_REASON_RECREATE_SPLIT,
             EXIT_REASON_FULLSCREEN_SHORTCUT,
+            EXIT_REASON_ENTER_DESKTOP
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
@@ -1011,6 +1012,8 @@
                 return "CHILD_TASK_ENTER_PIP";
             case EXIT_REASON_RECREATE_SPLIT:
                 return "RECREATE_SPLIT";
+            case EXIT_REASON_ENTER_DESKTOP:
+                return "ENTER_DESKTOP";
             default:
                 return "unknown reason, reason int = " + exitReason;
         }
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 5483fa5..f4ab226 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
@@ -25,6 +25,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
@@ -42,6 +43,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -192,6 +194,8 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
             case EXIT_REASON_FULLSCREEN_SHORTCUT:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+            case EXIT_REASON_ENTER_DESKTOP:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
             case EXIT_REASON_UNKNOWN:
                 // Fall through
             default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index cb76044..edb5aba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -20,7 +20,6 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
 
 import android.animation.Animator;
-import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.Slog;
@@ -47,7 +46,7 @@
     private final int mIconFadeOutDuration;
     private final int mAppRevealDelay;
     private final int mAppRevealDuration;
-    @ExitAnimationType
+    @SplashScreenExitAnimationUtils.ExitAnimationType
     private final int mAnimationType;
     private final int mAnimationDuration;
     private final float mIconStartAlpha;
@@ -58,24 +57,6 @@
 
     private Runnable mFinishCallback;
 
-    /**
-    * This splash screen exit animation type uses a radial vanish to hide
-    * the starting window and slides up the main window content.
-    */
-    private static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
-
-    /**
-    * This splash screen exit animation type fades out the starting window
-    * to reveal the main window content.
-    */
-    private static final int TYPE_FADE_OUT = 1;
-
-    @IntDef(prefix = { "TYPE_" }, value = {
-        TYPE_RADIAL_VANISH_SLIDE_UP,
-        TYPE_FADE_OUT,
-    })
-    private @interface ExitAnimationType {}
-
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
             Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
             float roundedCornerRadius) {
@@ -121,15 +102,10 @@
     }
 
     void startAnimations() {
-        if (mAnimationType == TYPE_FADE_OUT) {
-            SplashScreenExitAnimationUtils.startFadeOutAnimation(mSplashScreenView,
-                    mAnimationDuration, this);
-        } else {
-            SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
-                    mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
-                    mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
-                    mAppRevealDuration, this, mRoundedCornerRadius);
-        }
+        SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView,
+                mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame,
+                mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha,
+                mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius);
     }
 
     private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 64f09a7..e86b62d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -20,6 +20,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.IntDef;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -54,6 +55,7 @@
 public class SplashScreenExitAnimationUtils {
     private static final boolean DEBUG_EXIT_ANIMATION = false;
     private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+    private static final boolean DEBUG_EXIT_FADE_ANIMATION = false;
     private static final String TAG = "SplashScreenExitAnimationUtils";
 
     private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
@@ -62,19 +64,47 @@
     private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
 
     /**
+     * This splash screen exit animation type uses a radial vanish to hide
+     * the starting window and slides up the main window content.
+     * @hide
+     */
+    public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
+
+    /**
+     * This splash screen exit animation type fades out the starting window
+     * to reveal the main window content.
+     * @hide
+     */
+    public static final int TYPE_FADE_OUT = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_RADIAL_VANISH_SLIDE_UP,
+            TYPE_FADE_OUT,
+    })
+    public @interface ExitAnimationType {}
+
+    /**
      * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
      * window with rounded corner radius.
      */
-    static void startAnimations(ViewGroup splashScreenView,
-            SurfaceControl firstWindowSurface, int mainWindowShiftLength,
-            TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
-            int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
-            int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
-            float roundedCornerRadius) {
-        ValueAnimator animator = createRadialVanishSlideUpAnimator(splashScreenView,
-                firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
-                animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
-                appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+    static void startAnimations(@ExitAnimationType int animationType,
+            ViewGroup splashScreenView, SurfaceControl firstWindowSurface,
+            int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame,
+            int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+            float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+            Animator.AnimatorListener animatorListener, float roundedCornerRadius) {
+        ValueAnimator animator;
+        if (animationType == TYPE_FADE_OUT) {
+            animator = createFadeOutAnimation(splashScreenView, animationDuration,
+                    iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay,
+                    appRevealDuration, animatorListener);
+        } else {
+            animator = createRadialVanishSlideUpAnimator(splashScreenView,
+                    firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+                    animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+                    appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+        }
         animator.start();
     }
 
@@ -88,10 +118,11 @@
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
             int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
-        startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
-                transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
-                iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
-                animatorListener, 0f /* roundedCornerRadius */);
+        // Start the default 'reveal' animation.
+        startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, splashScreenView,
+                firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+                animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+                appRevealDelay, appRevealDuration, animatorListener, 0f /* roundedCornerRadius */);
     }
 
     /**
@@ -209,18 +240,57 @@
         return nightMode == Configuration.UI_MODE_NIGHT_YES;
     }
 
-    static void startFadeOutAnimation(ViewGroup splashScreenView,
-            int animationDuration, Animator.AnimatorListener animatorListener) {
-        final ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
+    private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView,
+            int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+            float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+            Animator.AnimatorListener animatorListener) {
+
+        if (DEBUG_EXIT_FADE_ANIMATION) {
+            splashScreenView.setBackgroundColor(Color.BLUE);
+        }
+
+        final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
         animator.setDuration(animationDuration);
         animator.setInterpolator(Interpolators.LINEAR);
         animator.addUpdateListener(animation -> {
-            splashScreenView.setAlpha((float) animation.getAnimatedValue());
+
+            float linearProgress = (float) animation.getAnimatedValue();
+
+            // Icon fade out progress (always starts immediately)
+            final float iconFadeProgress = ICON_INTERPOLATOR.getInterpolation(getProgress(
+                            linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+            View iconView = null;
+            View brandingView = null;
+
+            if (splashScreenView instanceof SplashScreenView) {
+                iconView = ((SplashScreenView) splashScreenView).getIconView();
+                brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+            }
+            if (iconView != null) {
+                iconView.setAlpha(iconStartAlpha * (1f - iconFadeProgress));
+            }
+            if (brandingView != null) {
+                brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress));
+            }
+
+            // Splash screen fade out progress (possibly delayed)
+            final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation(
+                    getProgress(linearProgress, appRevealDelay,
+                    appRevealDuration, animationDuration));
+
+            splashScreenView.setAlpha(1f - splashFadeProgress);
+
+            if (DEBUG_EXIT_FADE_ANIMATION) {
+                Slog.d(TAG, "progress -> animation: " + linearProgress
+                        + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a")
+                        + "\t splash alpha: " + splashScreenView.getAlpha()
+                );
+            }
         });
         if (animatorListener != null) {
             animator.addListener(animatorListener);
         }
-        animator.start();
+        return animator;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index adae21b..93d7636 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -54,12 +54,13 @@
     private static final String TAG = TaskViewTaskController.class.getSimpleName();
 
     private final CloseGuard mGuard = new CloseGuard();
-
+    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    /** Used to inset the activity content to allow space for a caption bar. */
+    private final Binder mCaptionInsetsOwner = new Binder();
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Executor mShellExecutor;
     private final SyncTransactionQueue mSyncQueue;
     private final TaskViewTransitions mTaskViewTransitions;
-    private TaskViewBase mTaskViewBase;
     private final Context mContext;
 
     /**
@@ -70,21 +71,19 @@
      * in this situation to allow us to notify listeners correctly if the task failed to open.
      */
     private ActivityManager.RunningTaskInfo mPendingInfo;
-    /* Indicates that the task we attempted to launch in the task view failed to launch. */
-    private boolean mTaskNotFound;
+    private TaskViewBase mTaskViewBase;
     protected ActivityManager.RunningTaskInfo mTaskInfo;
     private WindowContainerToken mTaskToken;
     private SurfaceControl mTaskLeash;
-    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+    /* Indicates that the task we attempted to launch in the task view failed to launch. */
+    private boolean mTaskNotFound;
     private boolean mSurfaceCreated;
     private SurfaceControl mSurfaceControl;
     private boolean mIsInitialized;
     private boolean mNotifiedForInitialized;
+    private boolean mHideTaskWithSurface = true;
     private TaskView.Listener mListener;
     private Executor mListenerExecutor;
-
-    /** Used to inset the activity content to allow space for a caption bar. */
-    private final Binder mCaptionInsetsOwner = new Binder();
     private Rect mCaptionInsets;
 
     public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
@@ -102,6 +101,19 @@
         mGuard.open("release");
     }
 
+    /**
+     * Specifies if the task should be hidden when the surface is destroyed.
+     * <p>This is {@code true} by default.
+     *
+     * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
+     *                            surface is destroyed, {@code true} otherwise.
+     */
+    public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
+        // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
+        // are moved to a window in SystemUI in auto.
+        mHideTaskWithSurface = hideTaskWithSurface;
+    }
+
     SurfaceControl getSurfaceControl() {
         return mSurfaceControl;
     }
@@ -257,9 +269,17 @@
         mTaskNotFound = false;
     }
 
+    /** This method shouldn't be called when shell transitions are enabled. */
     private void updateTaskVisibility() {
+        boolean visible = mSurfaceCreated;
+        if (!visible && !mHideTaskWithSurface) {
+            return;
+        }
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+        wct.setHidden(mTaskToken, !visible /* hidden */);
+        if (!visible) {
+            wct.reorder(mTaskToken, false /* onTop */);
+        }
         mSyncQueue.queue(wct);
         if (mListener == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 026d0cd..b4e1818 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -68,6 +68,7 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -348,13 +349,8 @@
             final int id = v.getId();
             if (id == R.id.close_window || id == R.id.close_button) {
                 mTaskOperations.closeTask(mTaskToken);
-                if (mSplitScreenController != null
-                        && mSplitScreenController.isSplitScreenVisible()) {
-                    int remainingTaskPosition = mTaskId == mSplitScreenController
-                            .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
-                            ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
-                    ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
-                            .getTaskInfo(remainingTaskPosition);
+                if (isTaskInSplitScreen(mTaskId)) {
+                    RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId);
                     mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
                 }
                 decoration.closeMaximizeMenu();
@@ -376,6 +372,7 @@
                     mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
                     decoration.incrementRelayoutBlock();
                     mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+                    closeOtherSplitTask(mTaskId);
                 }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
@@ -720,6 +717,7 @@
                             relevantDecor.mTaskInfo.displayId);
                     if (ev.getY() > statusBarHeight) {
                         if (mMoveToDesktopAnimator == null) {
+                            closeOtherSplitTask(relevantDecor.mTaskInfo.taskId);
                             mMoveToDesktopAnimator = new MoveToDesktopAnimator(
                                     mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
                                     relevantDecor.mTaskSurface);
@@ -810,7 +808,8 @@
     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
         if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
             // We can't look at focused task here as only one task will have focus.
-            return getSplitScreenDecor(ev);
+            DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
+            return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
         } else {
             return getFocusedDecor();
         }
@@ -942,6 +941,24 @@
         }
     }
 
+    private RunningTaskInfo getOtherSplitTask(int taskId) {
+        @SplitPosition int remainingTaskPosition = mSplitScreenController
+                .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
+                ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+        return mSplitScreenController.getTaskInfo(remainingTaskPosition);
+    }
+
+    private void closeOtherSplitTask(int taskId) {
+        if (isTaskInSplitScreen(taskId)) {
+            mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
+        }
+    }
+
+    private boolean isTaskInSplitScreen(int taskId) {
+        return mSplitScreenController != null
+                && mSplitScreenController.isTaskInSplitScreen(taskId);
+    }
+
     private class DragStartListenerImpl
             implements DragPositioningCallbackUtility.DragStartListener {
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 8d76fc6..a269275 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.windowdecor;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
@@ -220,6 +221,8 @@
         final Resources resources = mDecorWindowContext.getResources();
         final Configuration taskConfig = mTaskInfo.getConfiguration();
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+        final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode()
+                == WINDOWING_MODE_FULLSCREEN;
         outResult.mWidth = taskBounds.width();
         outResult.mHeight = taskBounds.height();
 
@@ -279,13 +282,24 @@
         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
         final Point taskPosition = mTaskInfo.positionInParent;
-        startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
-                .setShadowRadius(mTaskSurface, shadowRadius)
+        if (isFullscreen) {
+            // Setting the task crop to the width/height stops input events from being sent to
+            // some regions of the app window. See b/300324920
+            // TODO(b/296921174): investigate whether crop/position needs to be set by window
+            // decorations at all when transition handlers are already taking ownership of the task
+            // surface placement/crop, especially when in fullscreen where tasks cannot be
+            // drag-resized by the window decoration.
+            startT.setWindowCrop(mTaskSurface, null);
+            finishT.setWindowCrop(mTaskSurface, null);
+        } else {
+            startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+            finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+        }
+        startT.setShadowRadius(mTaskSurface, shadowRadius)
                 .setColor(mTaskSurface, mTmpColor)
                 .show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
-                .setShadowRadius(mTaskSurface, shadowRadius)
-                .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+                .setShadowRadius(mTaskSurface, shadowRadius);
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
             finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index c335d3d..2bd6d57 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -80,7 +80,9 @@
                 secondAppForSplitScreen.launchViaIntent(wmHelper)
                 pipApp.launchViaIntent(wmHelper)
                 tapl.goHome()
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
+                SplitScreenUtils.enterSplit(
+                    wmHelper, tapl, device, pipApp, secondAppForSplitScreen,
+                    flicker.scenario.startRotation)
                 pipApp.enableAutoEnterForPipActivity()
             }
             teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index fb6c093..c9a98c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -33,14 +33,14 @@
     @Postsubmit
     @Test
     override fun pipAppWindowAlwaysVisible() {
-        flicker.assertWm { this.isAppWindowVisible(standardAppHelper) }
+        flicker.assertWm { this.isAppWindowVisible(standardAppHelper.packageNameMatcher) }
     }
 
     /** Checks [standardAppHelper] layer remains visible throughout the animation */
     @Postsubmit
     @Test
     override fun pipAppLayerAlwaysVisible() {
-        flicker.assertLayers { this.isVisible(standardAppHelper) }
+        flicker.assertLayers { this.isVisible(standardAppHelper.packageNameMatcher) }
     }
 
     /** Checks the content overlay appears then disappears during the animation */
@@ -57,7 +57,9 @@
     @Postsubmit
     @Test
     override fun pipWindowRemainInsideVisibleBounds() {
-        flicker.assertWmVisibleRegion(standardAppHelper) { coversAtMost(displayBounds) }
+        flicker.assertWmVisibleRegion(standardAppHelper.packageNameMatcher) {
+            coversAtMost(displayBounds)
+        }
     }
 
     /**
@@ -68,7 +70,7 @@
     @Test
     override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
         flicker.assertLayersVisibleRegion(
-            standardAppHelper.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
+            standardAppHelper.packageNameMatcher.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)
         ) {
             coversAtMost(displayBounds)
         }
@@ -93,9 +95,9 @@
     @Test
     override fun pipWindowBecomesPinned() {
         flicker.assertWm {
-            invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper) }
+            invoke("pipWindowIsNotPinned") { it.isNotPinned(standardAppHelper.packageNameMatcher) }
                 .then()
-                .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper) }
+                .invoke("pipWindowIsPinned") { it.isPinned(standardAppHelper.packageNameMatcher) }
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 54b3e2a8..d7ba3d5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -16,19 +16,22 @@
 
 package com.android.wm.shell.flicker.pip.apps
 
-import android.os.Handler
-import android.os.Looper
-import android.os.SystemClock
 import android.content.Context
 import android.location.Criteria
 import android.location.Location
 import android.location.LocationManager
+import android.os.Handler
+import android.os.Looper
+import android.os.SystemClock
+import android.platform.test.annotations.Postsubmit
 import android.tools.device.apphelpers.MapsAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
+import org.junit.Assume
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -129,4 +132,12 @@
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { tapl.goHome() }
     }
+
+    @Postsubmit
+    @Test
+    override fun focusChanges() {
+        // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.focusChanges()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
index ad34634..c370d91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/YouTubeEnterPipTest.kt
@@ -16,13 +16,16 @@
 
 package com.android.wm.shell.flicker.pip.apps
 
+import android.platform.test.annotations.Postsubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.apphelpers.YouTubeAppHelper
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
+import org.junit.Assume
 import org.junit.FixMethodOrder
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
 import org.junit.runners.Parameterized
@@ -75,4 +78,18 @@
     override val thisTransition: FlickerBuilder.() -> Unit = {
         transitions { tapl.goHome() }
     }
+
+    @Postsubmit
+    @Test
+    override fun pipOverlayLayerAppearThenDisappear() {
+        // YouTube uses source rect hint, so PiP overlay is never present
+    }
+
+    @Postsubmit
+    @Test
+    override fun focusChanges() {
+        // in gestural nav the focus goes to different activity on swipe up with auto enter PiP
+        Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+        super.focusChanges()
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 245184c..80ab24d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -51,7 +51,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp, rotation)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index 1f2f1ec..4c39104 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -49,7 +49,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index ebbf7c5..f6d1afc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -49,7 +49,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 71e701c..db5a32a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -49,7 +49,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index f2cbf24..3a6a4a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -61,7 +61,7 @@
 
     @Test
     open fun enterSplitScreenFromOverview() {
-        SplitScreenUtils.splitFromOverview(tapl, device)
+        SplitScreenUtils.splitFromOverview(tapl, device, rotation)
         SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 538ed96..6330d33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -50,9 +50,10 @@
     fun setup() {
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
+
         tapl.workspace.switchToOverview().dismissAllTasks()
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 0dab5ad..64b75c5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -50,7 +50,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
 
         thirdApp.launchViaIntent(wmHelper)
         wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index ad3a2d4..1795010 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -49,7 +49,7 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
 
         tapl.goHome()
         wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index b780a16..6ff8c53 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -50,7 +50,7 @@
         tapl.setExpectedRotation(rotation.value)
         tapl.workspace.switchToOverview().dismissAllTasks()
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
 
         tapl.goHome()
         wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 329d61d..251cb50 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -51,8 +51,8 @@
         tapl.setEnableRotation(true)
         tapl.setExpectedRotation(rotation.value)
 
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-        SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
+        SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp, rotation)
         SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
     }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
index e59ed64..1387536 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -62,8 +62,10 @@
         get() = {
             setup {
                 tapl.goHome()
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, pipApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                    secondaryApp, flicker.scenario.startRotation)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, pipApp,
+                    flicker.scenario.startRotation)
                 pipApp.enableAutoEnterForPipActivity()
                 SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, pipApp)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index 4d90070..3b9e53f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -39,7 +39,8 @@
     protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                textEditApp, flicker.scenario.startRotation) }
             transitions {
                 SplitScreenUtils.copyContentInSplit(
                     instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 8360e94..5fdde3a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -35,7 +35,8 @@
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                secondaryApp, flicker.scenario.startRotation) }
             transitions {
                 if (tapl.isTablet) {
                     SplitScreenUtils.dragDividerToDismissSplit(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index e745878..b7f6bfe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -35,7 +35,10 @@
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp,
+                    flicker.scenario.startRotation)
+            }
             transitions {
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index c3beb36..bb2a7aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -37,7 +37,8 @@
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                secondaryApp, flicker.scenario.startRotation) }
             transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index be507d8..5e5e7d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -46,7 +46,7 @@
                     .waitForAndVerify()
             }
             transitions {
-                SplitScreenUtils.splitFromOverview(tapl, device)
+                SplitScreenUtils.splitFromOverview(tapl, device, flicker.scenario.startRotation)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index ed0debd..46b0bd2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -39,7 +39,8 @@
     SplitScreenBase(flicker) {
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                secondaryApp, flicker.scenario.startRotation) }
             transitions {
                 SplitScreenUtils.doubleTapDividerToSwitch(device)
                 wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 9b7939a..baf7693 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -39,7 +39,8 @@
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                    secondaryApp, flicker.scenario.startRotation)
 
                 thirdApp.launchViaIntent(wmHelper)
                 wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index 9326ef3..33b55f1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -37,7 +37,8 @@
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                    secondaryApp, flicker.scenario.startRotation)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index b928e40..b79dfb5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -37,7 +37,8 @@
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                    secondaryApp, flicker.scenario.startRotation)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index f314995..0204d75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -39,8 +39,10 @@
     protected val thisTransition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
-                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+                    secondaryApp, flicker.scenario.startRotation)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp,
+                    flicker.scenario.startRotation)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
             }
             transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 3f8a1ae..87e3860 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -19,6 +19,7 @@
 import android.app.Instrumentation
 import android.graphics.Point
 import android.os.SystemClock
+import android.tools.common.Rotation
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentMatcher
 import android.tools.common.traces.component.IComponentNameMatcher
@@ -41,6 +42,7 @@
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
 import org.junit.Assert.assertNotNull
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 
 object SplitScreenUtils {
     private const val TIMEOUT_MS = 3_000L
@@ -101,13 +103,14 @@
         tapl: LauncherInstrumentation,
         device: UiDevice,
         primaryApp: StandardAppHelper,
-        secondaryApp: StandardAppHelper
+        secondaryApp: StandardAppHelper,
+        rotation: Rotation
     ) {
         primaryApp.launchViaIntent(wmHelper)
         secondaryApp.launchViaIntent(wmHelper)
         tapl.goHome()
         wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl, device)
+        splitFromOverview(tapl, device, rotation)
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
@@ -121,7 +124,7 @@
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
-    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice, rotation: Rotation) {
         // 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.
@@ -129,7 +132,9 @@
             // TAPL's currentTask on tablet is sometimes not what we expected if the overview
             // contains more than 3 task views. We need to use uiautomator directly to find the
             // second task to split.
-            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val home = tapl.workspace.switchToOverview()
+            ChangeDisplayOrientationRule.setRotation(rotation)
+            home.overviewActions.clickSplit()
             val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
             if (snapshots == null || snapshots.size < 1) {
                 error("Fail to find a overview snapshot to split.")
@@ -145,9 +150,10 @@
             }
             snapshots[0].click()
         } else {
-            tapl.workspace
+            val home = tapl.workspace
                 .switchToOverview()
-                .currentTask
+            ChangeDisplayOrientationRule.setRotation(rotation)
+            home.currentTask
                 .tapMenu()
                 .tapSplitMenuItem()
                 .currentTask
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 9f0d89b..5237585 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -27,15 +27,13 @@
 import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -66,7 +64,7 @@
 
     @Before
     fun setup() {
-        launcherApps = mock(LauncherApps::class.java)
+        launcherApps = mock<LauncherApps>()
         repository = BubbleVolatileRepository(launcherApps)
     }
 
@@ -98,7 +96,7 @@
         repository.addBubbles(user11.identifier, listOf(bubble12))
         assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier))
 
-        Mockito.verifyNoMoreInteractions(launcherApps)
+        verifyNoMoreInteractions(launcherApps)
     }
 
     @Test
@@ -167,9 +165,8 @@
         assertThat(ret).isTrue() // bubbles were removed
 
         assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
     }
 
     @Test
@@ -184,9 +181,8 @@
 
         assertThat(repository.getEntities(user0.identifier).toList())
                 .isEqualTo(listOf(bubble1, bubble3))
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
     }
 
     @Test
@@ -200,9 +196,8 @@
 
         assertThat(repository.getEntities(user0.identifier).toList())
                 .isEqualTo(listOf(bubble1, bubble2, bubble3))
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
     }
 
     @Test
@@ -219,9 +214,8 @@
                 user11.identifier))
         assertThat(ret).isFalse() // bubbles were NOT removed
 
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
     }
 
     @Test
@@ -237,9 +231,8 @@
         assertThat(ret).isTrue() // bubbles were removed
 
         assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
 
         // User 11 bubbles should still be here
         assertThat(repository.getEntities(user11.identifier).toList())
@@ -261,9 +254,8 @@
         // bubble2 is the work profile bubble and should be removed
         assertThat(repository.getEntities(user0.identifier).toList())
                 .isEqualTo(listOf(bubble1, bubble3))
-        verify(launcherApps, never()).uncacheShortcuts(anyString(),
-                any(),
-                any(UserHandle::class.java), anyInt())
+        verify(launcherApps, never())
+                .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
 
         // User 11 bubbles should still be here
         assertThat(repository.getEntities(user11.identifier).toList())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index be4a287..c6cccc0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -52,6 +52,8 @@
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
@@ -94,6 +96,7 @@
             ToggleResizeDesktopTaskTransitionHandler
     @Mock lateinit var launchAdjacentController: LaunchAdjacentController
     @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
+    @Mock lateinit var splitScreenController: SplitScreenController
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -116,6 +119,7 @@
         whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
 
         controller = createController()
+        controller.splitScreenController = splitScreenController
 
         shellInit.init()
     }
@@ -341,6 +345,30 @@
     }
 
     @Test
+    fun moveToDesktop_splitTaskExitsSplit() {
+        var task = setUpSplitScreenTask()
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+        verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+            eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+        )
+    }
+
+    @Test
+    fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
+        var task = setUpFullscreenTask()
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
+        assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+        verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+            eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+        )
+    }
+
+    @Test
     fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
@@ -695,6 +723,13 @@
         return task
     }
 
+    private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+        val task = createSplitScreenTask(displayId)
+        whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+        runningTasks.add(task)
+        return task
+    }
+
     private fun markTaskVisible(task: RunningTaskInfo) {
         desktopModeTaskRepository.updateVisibleFreeformTasks(
             task.displayId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 29a757c..2f6f320 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.view.Display.DEFAULT_DISPLAY
 import com.android.wm.shell.MockToken
 import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -45,12 +46,25 @@
         @JvmOverloads
         fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
             return TestRunningTaskInfoBuilder()
-                    .setDisplayId(displayId)
-                    .setToken(MockToken().token())
-                    .setActivityType(ACTIVITY_TYPE_STANDARD)
-                    .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                    .setLastActiveTime(100)
-                    .build()
+                .setDisplayId(displayId)
+                .setToken(MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLastActiveTime(100)
+                .build()
+        }
+
+        /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+        @JvmStatic
+        @JvmOverloads
+        fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+            return TestRunningTaskInfoBuilder()
+                .setDisplayId(displayId)
+                .setToken(MockToken().token())
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+                .setLastActiveTime(100)
+                .build()
         }
 
         /** Create a new home task */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index d098d33..0088051 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -221,6 +221,20 @@
     }
 
     @Test
+    public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() {
+        assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
+        mTaskViewTaskController.setHideTaskWithSurface(false);
+
+        SurfaceHolder sh = mock(SurfaceHolder.class);
+        mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+        mTaskView.surfaceCreated(sh);
+        reset(mViewListener);
+        mTaskView.surfaceDestroyed(sh);
+
+        verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+    }
+
+    @Test
     public void testSurfaceDestroyed_withTask_legacyTransitions() {
         assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
         SurfaceHolder sh = mock(SurfaceHolder.class);
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 436f107..ef4cc46 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,5 +1,4 @@
 set noparent
-toddke@google.com
 zyy@google.com
 patb@google.com
 
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 735fc07..30d4612 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -169,7 +169,8 @@
         return;
     }
     mGrContext->flushAndSubmit();
-    mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+    mGrContext->performDeferredCleanup(std::chrono::seconds(30),
+                                       GrPurgeResourceOptions::kAllResources);
 }
 
 void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 86e53f5..83d2b61 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -13,3 +13,10 @@
     description: "Gates whether to adjust local stream volume when the app in the foreground is the last app to play audio or adjust the volume of the last active media session that the user interacted with."
     bug: "275185436"
 }
+
+flag {
+     namespace: "media_solutions"
+     name: "enable_audio_policies_device_and_bluetooth_controller"
+     description: "Use Audio Policies implementation for device and Bluetooth route controllers."
+     bug: "280576228"
+}
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index 3b70890..f9eaabd 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -25,6 +25,8 @@
 import android.hardware.tv.tuner.LnbTone;
 import android.hardware.tv.tuner.LnbVoltage;
 import android.media.tv.tuner.Tuner.Result;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
+import android.util.Log;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -147,10 +149,13 @@
     public static final int EVENT_TYPE_LNB_OVERLOAD = LnbEventType.LNB_OVERLOAD;
 
     private static final String TAG = "Lnb";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     Map<LnbCallback, Executor> mCallbackMap =
             new HashMap<LnbCallback, Executor>();
     Tuner mOwner;
+    TunerResourceManager mTunerResourceManager;
+    int mClientId;
     private final Object mCallbackLock = new Object();
 
 
@@ -174,6 +179,10 @@
             }
         }
         setOwner(tuner);
+        if (mOwner != null) {
+            mTunerResourceManager = mOwner.getTunerResourceManager();
+            mClientId = mOwner.getClientId();
+        }
     }
 
     /**
@@ -210,6 +219,8 @@
         Objects.requireNonNull(newOwner, "newOwner must not be null");
         synchronized (mLock) {
             mOwner = newOwner;
+            mTunerResourceManager = newOwner.getTunerResourceManager();
+            mClientId = newOwner.getClientId();
         }
     }
 
@@ -317,21 +328,42 @@
      * Releases the LNB instance.
      */
     public void close() {
-        synchronized (mLock) {
-            if (mIsClosed) {
-                return;
-            }
-            int res = nativeClose();
-            if (res != Tuner.RESULT_SUCCESS) {
-                TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
-            } else {
-                mIsClosed = true;
-                if (mOwner != null) {
-                    mOwner.releaseLnb();
-                    mOwner = null;
+        acquireTRMSLock("close()");
+        try {
+            synchronized (mLock) {
+                if (mIsClosed) {
+                    return;
                 }
-                mCallbackMap.clear();
+                int res = nativeClose();
+                if (res != Tuner.RESULT_SUCCESS) {
+                    TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
+                } else {
+                    mIsClosed = true;
+                    if (mOwner != null) {
+                        mOwner.releaseLnb();
+                        mOwner = null;
+                    }
+                    mCallbackMap.clear();
+                }
             }
+        } finally {
+            releaseTRMSLock();
         }
     }
+
+    private void acquireTRMSLock(String functionNameForLog) {
+        if (DEBUG) {
+            Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
+                    + "for clientId:" + mClientId);
+        }
+        if (!mTunerResourceManager.acquireLock(mClientId)) {
+            Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog
+                    + " for clientId:" + mClientId + " - this can cause deadlock between"
+                    + " Tuner API calls and onReclaimResources()");
+        }
+    }
+
+    private void releaseTRMSLock() {
+        mTunerResourceManager.releaseLock(mClientId);
+    }
 }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index f87e47e..9924fae 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -903,6 +903,9 @@
         }
     }
 
+    /**
+     * Releases Lnb resource if held. TRMS lock must be acquired prior to calling this function.
+     */
     private void closeLnb() {
         mLnbLock.lock();
         try {
@@ -2806,6 +2809,10 @@
         return mClientId;
     }
 
+    /* package */ TunerResourceManager getTunerResourceManager() {
+        return mTunerResourceManager;
+    }
+
     private void acquireTRMSLock(String functionNameForLog) {
         if (DEBUG) {
             Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index 233aee2..fe26dc3 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -50,46 +50,3 @@
         proguard_compatibility: false,
     },
 }
-
-android_app {
-    name: "ClockworkCredentialManager",
-    defaults: ["platform_app_defaults"],
-    certificate: "platform",
-    manifest: "wear/AndroidManifest.xml",
-    srcs: ["wear/src/**/*.kt"],
-    resource_dirs: ["wear/res"],
-
-    dex_preopt: {
-        profile_guided: true,
-        profile: "wear/profile.txt.prof",
-    },
-
-    static_libs: [
-        "PlatformComposeCore",
-        "androidx.activity_activity-compose",
-        "androidx.appcompat_appcompat",
-        "androidx.compose.foundation_foundation",
-        "androidx.compose.foundation_foundation-layout",
-        "androidx.compose.material_material-icons-core",
-        "androidx.compose.material_material-icons-extended",
-        "androidx.compose.ui_ui",
-        "androidx.core_core-ktx",
-        "androidx.credentials_credentials",
-        "androidx.lifecycle_lifecycle-extensions",
-        "androidx.lifecycle_lifecycle-livedata",
-        "androidx.lifecycle_lifecycle-runtime-ktx",
-        "androidx.lifecycle_lifecycle-viewmodel-compose",
-        "androidx.wear.compose_compose-foundation",
-        "androidx.wear.compose_compose-material",
-        "kotlinx-coroutines-core",
-    ],
-
-    platform_apis: true,
-    privileged: true,
-
-    kotlincflags: ["-Xjvm-default=all"],
-
-    optimize: {
-        proguard_compatibility: false,
-    },
-}
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 4161601..d9ef3a2 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -17,31 +17,42 @@
  */
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.credentialmanager">
+          package="com.android.credentialmanager">
 
     <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
     <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:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
-        android:launchMode="singleTop"
+        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:excludeFromRecents="true"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
         android:theme="@style/Theme.CredentialSelector">
-    </activity>
-  </application>
+
+        <activity
+            android:name=".CredentialSelectorActivity"
+            android:exported="true"
+            android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+            android:launchMode="singleTop"
+            android:label="@string/app_name"
+            android:excludeFromRecents="true"
+            android:theme="@style/Theme.CredentialSelector">
+        </activity>
+        <service
+            android:name=".autofill.CredentialAutofillService"
+            android:exported="false"
+            android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+            <meta-data
+                android:name="android.autofill"
+                android:resource="@xml/autofill_service_configuration"/>
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService"/>
+            </intent-filter>
+        </service>
+    </application>
 
 </manifest>
diff --git a/packages/CredentialManager/horologist/Android.bp b/packages/CredentialManager/horologist/Android.bp
new file mode 100644
index 0000000..bb324bb
--- /dev/null
+++ b/packages/CredentialManager/horologist/Android.bp
@@ -0,0 +1,27 @@
+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"],
+}
+
+// TODO: ag/24733147 - Remove this project once it is imported.
+android_library {
+    name: "Horologist",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui",
+        "androidx.navigation_navigation-compose",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "androidx.wear.compose_compose-foundation",
+        "androidx.wear.compose_compose-material",
+        "androidx.wear.compose_compose-navigation",
+    ],
+}
diff --git a/packages/CredentialManager/horologist/AndroidManifest.xml b/packages/CredentialManager/horologist/AndroidManifest.xml
new file mode 100644
index 0000000..e386ce2
--- /dev/null
+++ b/packages/CredentialManager/horologist/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2023 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.google.android.horologist">
+
+    <uses-feature android:name="android.hardware.type.watch" />
+
+</manifest>
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/annotations/ExperimentalHorologistApi.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/annotations/ExperimentalHorologistApi.kt
new file mode 100644
index 0000000..ae77605
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/annotations/ExperimentalHorologistApi.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 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
+ *
+ *      https://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.google.android.horologist.annotations
+
+@RequiresOptIn(
+        message = "Horologist API is experimental. The API may be changed in the future.",
+)
+@Retention(AnnotationRetention.BINARY)
+public annotation class ExperimentalHorologistApi
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnDefaults.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnDefaults.kt
new file mode 100644
index 0000000..c88bbd8
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnDefaults.kt
@@ -0,0 +1,131 @@
+/*
+ * 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
+ *
+ *      https://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:Suppress("ObjectLiteralToLambda")
+
+package com.google.android.horologist.compose.layout
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState.RotaryMode
+
+/**
+ * Default layouts for ScalingLazyColumnState, based on UX guidance.
+ */
+public object ScalingLazyColumnDefaults {
+    /**
+     * Layout the first item, directly under the time text.
+     * This is positioned from the top of the screen instead of the
+     * center.
+     */
+    @ExperimentalHorologistApi
+    public fun belowTimeText(
+            rotaryMode: RotaryMode = RotaryMode.Scroll,
+            firstItemIsFullWidth: Boolean = false,
+            verticalArrangement: Arrangement.Vertical =
+                    Arrangement.spacedBy(
+                            space = 4.dp,
+                            alignment = Alignment.Top,
+                    ),
+            horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+            contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
+            topPaddingDp: Dp = 32.dp + (if (firstItemIsFullWidth) 20.dp else 0.dp),
+    ): ScalingLazyColumnState.Factory {
+        return object : ScalingLazyColumnState.Factory {
+            @Composable
+            override fun create(): ScalingLazyColumnState {
+                val density = LocalDensity.current
+                val configuration = LocalConfiguration.current
+
+                return remember {
+                    val screenHeightPx =
+                            with(density) { configuration.screenHeightDp.dp.roundToPx() }
+                    val topPaddingPx = with(density) { topPaddingDp.roundToPx() }
+                    val topScreenOffsetPx = screenHeightPx / 2 - topPaddingPx
+
+                    ScalingLazyColumnState(
+                            initialScrollPosition = ScalingLazyColumnState.ScrollPosition(
+                                    index = 0,
+                                    offsetPx = topScreenOffsetPx,
+                            ),
+                            anchorType = ScalingLazyListAnchorType.ItemStart,
+                            rotaryMode = rotaryMode,
+                            verticalArrangement = verticalArrangement,
+                            horizontalAlignment = horizontalAlignment,
+                            contentPadding = contentPadding,
+                    )
+                }
+            }
+        }
+    }
+
+    /**
+     * Layout the item [initialCenterIndex] at [initialCenterOffset] from the
+     * center of the screen.
+     */
+    @ExperimentalHorologistApi
+    public fun scalingLazyColumnDefaults(
+            rotaryMode: RotaryMode = RotaryMode.Scroll,
+            initialCenterIndex: Int = 1,
+            initialCenterOffset: Int = 0,
+            verticalArrangement: Arrangement.Vertical =
+                    Arrangement.spacedBy(
+                            space = 4.dp,
+                            alignment = Alignment.Top,
+                    ),
+            horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+            contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
+            autoCentering: AutoCenteringParams? = AutoCenteringParams(
+                    initialCenterIndex,
+                    initialCenterOffset,
+            ),
+            anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter,
+            hapticsEnabled: Boolean = true,
+            reverseLayout: Boolean = false,
+    ): ScalingLazyColumnState.Factory {
+        return object : ScalingLazyColumnState.Factory {
+            @Composable
+            override fun create(): ScalingLazyColumnState {
+                return remember {
+                    ScalingLazyColumnState(
+                            initialScrollPosition = ScalingLazyColumnState.ScrollPosition(
+                                    index = initialCenterIndex,
+                                    offsetPx = initialCenterOffset,
+                            ),
+                            rotaryMode = rotaryMode,
+                            verticalArrangement = verticalArrangement,
+                            horizontalAlignment = horizontalAlignment,
+                            contentPadding = contentPadding,
+                            autoCentering = autoCentering,
+                            anchorType = anchorType,
+                            hapticsEnabled = hapticsEnabled,
+                            reverseLayout = reverseLayout,
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnState.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnState.kt
new file mode 100644
index 0000000..3a12b9f
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScalingLazyColumnState.kt
@@ -0,0 +1,168 @@
+/*
+ * 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
+ *
+ *      https://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:Suppress("ObjectLiteralToLambda")
+@file:OptIn(ExperimentalHorologistApi::class, ExperimentalWearFoundationApi::class)
+
+package com.google.android.horologist.compose.layout
+
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.lazy.AutoCenteringParams
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.ScalingParams
+import androidx.wear.compose.foundation.rememberActiveFocusRequester
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState.RotaryMode
+import com.google.android.horologist.compose.rotaryinput.rememberDisabledHaptic
+import com.google.android.horologist.compose.rotaryinput.rememberRotaryHapticHandler
+import com.google.android.horologist.compose.rotaryinput.rotaryWithScroll
+import com.google.android.horologist.compose.rotaryinput.rotaryWithSnap
+import com.google.android.horologist.compose.rotaryinput.toRotaryScrollAdapter
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults as WearScalingLazyColumnDefaults
+
+/**
+ * A Config and State object wrapping up all configuration for a [ScalingLazyColumn].
+ * This allows defaults such as [ScalingLazyColumnDefaults.belowTimeText].
+ */
+@ExperimentalHorologistApi
+public class ScalingLazyColumnState(
+        public val initialScrollPosition: ScrollPosition = ScrollPosition(1, 0),
+        public val autoCentering: AutoCenteringParams? = AutoCenteringParams(
+                initialScrollPosition.index,
+                initialScrollPosition.offsetPx,
+        ),
+        public val anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter,
+        public val contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
+        public val rotaryMode: RotaryMode = RotaryMode.Scroll,
+        public val reverseLayout: Boolean = false,
+        public val verticalArrangement: Arrangement.Vertical =
+                Arrangement.spacedBy(
+                        space = 4.dp,
+                        alignment = if (!reverseLayout) Alignment.Top else Alignment.Bottom,
+                ),
+        public val horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
+        public val flingBehavior: FlingBehavior? = null,
+        public val userScrollEnabled: Boolean = true,
+        public val scalingParams: ScalingParams = WearScalingLazyColumnDefaults.scalingParams(),
+        public val hapticsEnabled: Boolean = true,
+) {
+    private var _state: ScalingLazyListState? = null
+    public var state: ScalingLazyListState
+        get() {
+            if (_state == null) {
+                _state = ScalingLazyListState(
+                        initialScrollPosition.index,
+                        initialScrollPosition.offsetPx,
+                )
+            }
+            return _state!!
+        }
+        set(value) {
+            _state = value
+        }
+
+    public sealed interface RotaryMode {
+        public object Snap : RotaryMode
+        public object Scroll : RotaryMode
+
+        @Deprecated(
+                "Use RotaryMode.Scroll instead",
+                replaceWith = ReplaceWith("RotaryMode.Scroll"),
+        )
+        public object Fling : RotaryMode
+    }
+
+    public data class ScrollPosition(
+            val index: Int,
+            val offsetPx: Int,
+    )
+
+    public fun interface Factory {
+        @Composable
+        public fun create(): ScalingLazyColumnState
+    }
+}
+
+@Composable
+public fun rememberColumnState(
+        factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.belowTimeText(),
+): ScalingLazyColumnState {
+    val columnState = factory.create()
+
+    columnState.state = rememberSaveable(saver = ScalingLazyListState.Saver) {
+        columnState.state
+    }
+
+    return columnState
+}
+
+@ExperimentalHorologistApi
+@Composable
+public fun ScalingLazyColumn(
+        columnState: ScalingLazyColumnState,
+        modifier: Modifier = Modifier,
+        content: ScalingLazyListScope.() -> Unit,
+) {
+    val focusRequester = rememberActiveFocusRequester()
+
+    val rotaryHaptics = if (columnState.hapticsEnabled) {
+        rememberRotaryHapticHandler(columnState.state)
+    } else {
+        rememberDisabledHaptic()
+    }
+    val modifierWithRotary = when (columnState.rotaryMode) {
+        RotaryMode.Snap -> modifier.rotaryWithSnap(
+                focusRequester = focusRequester,
+                rotaryScrollAdapter = columnState.state.toRotaryScrollAdapter(),
+                reverseDirection = columnState.reverseLayout,
+                rotaryHaptics = rotaryHaptics,
+        )
+
+        else -> modifier.rotaryWithScroll(
+                focusRequester = focusRequester,
+                scrollableState = columnState.state,
+                reverseDirection = columnState.reverseLayout,
+                rotaryHaptics = rotaryHaptics,
+        )
+    }
+
+    ScalingLazyColumn(
+            modifier = modifierWithRotary,
+            state = columnState.state,
+            contentPadding = columnState.contentPadding,
+            reverseLayout = columnState.reverseLayout,
+            verticalArrangement = columnState.verticalArrangement,
+            horizontalAlignment = columnState.horizontalAlignment,
+            flingBehavior = columnState.flingBehavior ?: ScrollableDefaults.flingBehavior(),
+            userScrollEnabled = columnState.userScrollEnabled,
+            scalingParams = columnState.scalingParams,
+            anchorType = columnState.anchorType,
+            autoCentering = columnState.autoCentering,
+            content = content,
+    )
+}
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScrollAway.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScrollAway.kt
new file mode 100644
index 0000000..623ae1a
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/layout/ScrollAway.kt
@@ -0,0 +1,51 @@
+/*
+ * 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
+ *
+ *      https://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.google.android.horologist.compose.layout
+
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.State
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalDensity
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.material.scrollAway
+import com.google.android.horologist.compose.navscaffold.ScalingLazyColumnScrollableState
+
+internal fun Modifier.scrollAway(
+        scrollState: State<ScrollableState?>,
+): Modifier = composed {
+    when (val state = scrollState.value) {
+        is ScalingLazyColumnScrollableState -> {
+            val offsetDp = with(LocalDensity.current) {
+                state.initialOffsetPx.toDp()
+            }
+            this.scrollAway(state.scalingLazyListState, state.initialIndex, offsetDp)
+        }
+        is ScalingLazyListState -> this.scrollAway(state)
+        is LazyListState -> this.scrollAway(state)
+        is ScrollState -> this.scrollAway(state)
+        // Disabled
+        null -> this.hidden()
+        // Enabled but no scroll state
+        else -> this
+    }
+}
+
+internal fun Modifier.hidden(): Modifier = layout { _, _ -> layout(0, 0) {} }
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/NavScaffoldViewModel.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/NavScaffoldViewModel.kt
new file mode 100644
index 0000000..14c0ba1
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/NavScaffoldViewModel.kt
@@ -0,0 +1,297 @@
+/*
+ * 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
+ *
+ *      https://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:OptIn(ExperimentalHorologistApi::class, SavedStateHandleSaveableApi::class)
+
+package com.google.android.horologist.compose.navscaffold
+
+import android.os.Bundle
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewmodel.compose.SavedStateHandleSaveableApi
+import androidx.lifecycle.viewmodel.compose.saveable
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavHostController
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.material.PositionIndicator
+import androidx.wear.compose.material.Scaffold
+import androidx.wear.compose.material.TimeText
+import androidx.wear.compose.material.Vignette
+import androidx.wear.compose.material.VignettePosition
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.PositionIndicatorMode
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.TimeTextMode
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.TimeTextMode.ScrollAway
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.VignetteMode.Off
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.VignetteMode.On
+import com.google.android.horologist.compose.navscaffold.NavScaffoldViewModel.VignetteMode.WhenScrollable
+
+/**
+ * A ViewModel that backs the WearNavScaffold to allow each composable to interact and effect
+ * the [Scaffold] positionIndicator, vignette and timeText.
+ *
+ * A ViewModel is used to allow the same current instance to be shared between the WearNavScaffold
+ * and the composable screen via [NavHostController.currentBackStackEntry].
+ */
+public open class NavScaffoldViewModel(
+        private val savedStateHandle: SavedStateHandle,
+) : ViewModel() {
+    internal var initialIndex: Int? = null
+    internal var initialOffsetPx: Int? = null
+    internal var scrollType by mutableStateOf<ScrollType?>(null)
+
+    private lateinit var _scrollableState: ScrollableState
+
+    /**
+     * Returns the scrollable state for this composable or null if the scaffold should
+     * not consider this element to be scrollable.
+     */
+    public val scrollableState: ScrollableState?
+        get() = if (scrollType == null || scrollType == ScrollType.None) {
+            null
+        } else {
+            _scrollableState
+        }
+
+    /**
+     * The configuration of [Vignette], [WhenScrollable], [Off], [On] and if so whether top and
+     * bottom. Defaults to on for scrollable screens.
+     */
+    public var vignettePosition: VignetteMode by mutableStateOf(WhenScrollable)
+
+    /**
+     * The configuration of [TimeText], defaults to [TimeTextMode.ScrollAway] which will move the
+     * time text above the screen to avoid overlapping with the content moving up.
+     */
+    public var timeTextMode: TimeTextMode by mutableStateOf(ScrollAway)
+
+    /**
+     * The configuration of [PositionIndicator].  The default is to show a scroll bar while the
+     * scroll is in progress.
+     */
+    public var positionIndicatorMode: PositionIndicatorMode
+            by mutableStateOf(PositionIndicatorMode.On)
+
+    internal fun initializeScrollState(scrollStateBuilder: () -> ScrollState): ScrollState {
+        check(scrollType == null || scrollType == ScrollType.ScrollState)
+
+        if (scrollType == null) {
+            scrollType = ScrollType.ScrollState
+
+            _scrollableState = savedStateHandle.saveable(
+                    key = "navScaffold.ScrollState",
+                    saver = ScrollState.Saver,
+            ) {
+                scrollStateBuilder()
+            }
+        }
+
+        return _scrollableState as ScrollState
+    }
+
+    internal fun initializeScalingLazyListState(
+            scrollableStateBuilder: () -> ScalingLazyListState,
+    ): ScalingLazyListState {
+        check(scrollType == null || scrollType == ScrollType.ScalingLazyColumn)
+
+        if (scrollType == null) {
+            scrollType = ScrollType.ScalingLazyColumn
+
+            _scrollableState = savedStateHandle.saveable(
+                    key = "navScaffold.ScalingLazyListState",
+                    saver = ScalingLazyListState.Saver,
+            ) {
+                scrollableStateBuilder().also {
+                    initialIndex = it.centerItemIndex
+                    initialOffsetPx = it.centerItemScrollOffset
+                }
+            }
+        }
+
+        return _scrollableState as ScalingLazyListState
+    }
+
+    internal fun initializeScalingLazyListState(
+            columnState: ScalingLazyColumnState,
+    ) {
+        check(scrollType == null || scrollType == ScrollType.ScalingLazyColumn)
+
+        if (scrollType == null) {
+            scrollType = ScrollType.ScalingLazyColumn
+
+            initialIndex = columnState.initialScrollPosition.index
+            initialOffsetPx = columnState.initialScrollPosition.offsetPx
+
+            _scrollableState = savedStateHandle.saveable(
+                    key = "navScaffold.ScalingLazyListState",
+                    saver = ScalingLazyListState.Saver,
+            ) {
+                columnState.state
+            }
+        }
+
+        columnState.state = _scrollableState as ScalingLazyListState
+    }
+
+    internal fun initializeLazyList(
+            scrollableStateBuilder: () -> LazyListState,
+    ): LazyListState {
+        check(scrollType == null || scrollType == ScrollType.LazyList)
+
+        if (scrollType == null) {
+            scrollType = ScrollType.LazyList
+
+            _scrollableState = savedStateHandle.saveable(
+                    key = "navScaffold.LazyListState",
+                    saver = LazyListState.Saver,
+            ) {
+                scrollableStateBuilder()
+            }
+        }
+
+        return _scrollableState as LazyListState
+    }
+
+    internal enum class ScrollType {
+        None, ScalingLazyColumn, ScrollState, LazyList
+    }
+
+    /**
+     * The configuration of [TimeText], defaults to [ScrollAway] which will move the time text above the
+     * screen to avoid overlapping with the content moving up.
+     */
+    public enum class TimeTextMode {
+        On, Off, ScrollAway
+    }
+
+    /**
+     * The configuration of [PositionIndicator].  The default is to show a scroll bar while the
+     * scroll is in progress.
+     */
+    public enum class PositionIndicatorMode {
+        On, Off
+    }
+
+    /**
+     * The configuration of [Vignette], [WhenScrollable], [Off], [On] and if so whether top and
+     * bottom. Defaults to on for scrollable screens.
+     */
+    public sealed interface VignetteMode {
+        public object WhenScrollable : VignetteMode
+        public object Off : VignetteMode
+        public data class On(val position: VignettePosition) : VignetteMode
+    }
+
+    internal fun timeTextScrollableState(): ScrollableState? {
+        return when (timeTextMode) {
+            ScrollAway -> {
+                when (this.scrollType) {
+                    ScrollType.ScrollState -> {
+                        this.scrollableState as ScrollState
+                    }
+
+                    ScrollType.ScalingLazyColumn -> {
+                        val scalingLazyListState =
+                                this.scrollableState as ScalingLazyListState
+
+                        ScalingLazyColumnScrollableState(scalingLazyListState, initialIndex
+                                ?: 1, initialOffsetPx ?: 0)
+                    }
+
+                    ScrollType.LazyList -> {
+                        this.scrollableState as LazyListState
+                    }
+
+                    else -> {
+                        ScrollState(0)
+                    }
+                }
+            }
+
+            TimeTextMode.On -> {
+                ScrollState(0)
+            }
+
+            else -> {
+                null
+            }
+        }
+    }
+}
+
+internal class ScalingLazyColumnScrollableState(
+        val scalingLazyListState: ScalingLazyListState,
+        val initialIndex: Int,
+        val initialOffsetPx: Int,
+) : ScrollableState by scalingLazyListState
+
+/**
+ * The context items provided to a navigation composable.
+ *
+ * The [viewModel] can be used to customise the scaffold behaviour.
+ */
+public data class ScaffoldContext<T : ScrollableState>(
+        val backStackEntry: NavBackStackEntry,
+        val scrollableState: T,
+        val viewModel: NavScaffoldViewModel,
+) {
+    var timeTextMode: TimeTextMode by viewModel::timeTextMode
+
+    var positionIndicatorMode: PositionIndicatorMode by viewModel::positionIndicatorMode
+
+    val arguments: Bundle?
+        get() = backStackEntry.arguments
+}
+
+public data class NonScrollableScaffoldContext(
+        val backStackEntry: NavBackStackEntry,
+        val viewModel: NavScaffoldViewModel,
+) {
+    var timeTextMode: TimeTextMode by viewModel::timeTextMode
+
+    var positionIndicatorMode: PositionIndicatorMode by viewModel::positionIndicatorMode
+
+    val arguments: Bundle?
+        get() = backStackEntry.arguments
+}
+
+/**
+ * The context items provided to a navigation composable.
+ *
+ * The [viewModel] can be used to customise the scaffold behaviour.
+ */
+public data class ScrollableScaffoldContext(
+        val backStackEntry: NavBackStackEntry,
+        val columnState: ScalingLazyColumnState,
+        val viewModel: NavScaffoldViewModel,
+) {
+    val scrollableState: ScalingLazyListState
+        get() = columnState.state
+
+    var timeTextMode: TimeTextMode by viewModel::timeTextMode
+
+    var positionIndicatorMode: PositionIndicatorMode by viewModel::positionIndicatorMode
+
+    val arguments: Bundle?
+        get() = backStackEntry.arguments
+}
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/WearNavScaffold.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/WearNavScaffold.kt
new file mode 100644
index 0000000..315d822
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/navscaffold/WearNavScaffold.kt
@@ -0,0 +1,321 @@
+/*
+ * 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
+ *
+ *      https://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:OptIn(ExperimentalWearFoundationApi::class)
+
+package com.google.android.horologist.compose.navscaffold
+
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavDeepLink
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.HierarchicalFocusCoordinator
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.material.PositionIndicator
+import androidx.wear.compose.material.Scaffold
+import androidx.wear.compose.material.TimeText
+import androidx.wear.compose.material.Vignette
+import androidx.wear.compose.navigation.SwipeDismissableNavHost
+import androidx.wear.compose.navigation.SwipeDismissableNavHostState
+import androidx.wear.compose.navigation.composable
+import androidx.wear.compose.navigation.currentBackStackEntryAsState
+import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.layout.ScalingLazyColumnDefaults
+import com.google.android.horologist.compose.layout.ScalingLazyColumnState
+import com.google.android.horologist.compose.layout.scrollAway
+
+/**
+ * A Navigation and Scroll aware [Scaffold].
+ *
+ * In addition to [NavGraphBuilder.scrollable], 3 additional extensions are supported
+ * [scalingLazyColumnComposable], [scrollStateComposable] and
+ * [lazyListComposable].
+ *
+ * These should be used to build the [ScrollableState] or [FocusRequester] as well as
+ * configure the behaviour of [TimeText], [PositionIndicator] or [Vignette].
+ */
+@Composable
+public fun WearNavScaffold(
+        startDestination: String,
+        navController: NavHostController,
+        modifier: Modifier = Modifier,
+        snackbar: @Composable () -> Unit = {},
+        timeText: @Composable (Modifier) -> Unit = {
+            TimeText(
+                    modifier = it,
+            )
+        },
+        state: SwipeDismissableNavHostState = rememberSwipeDismissableNavHostState(),
+        builder: NavGraphBuilder.() -> Unit,
+) {
+    val currentBackStackEntry: NavBackStackEntry? by navController.currentBackStackEntryAsState()
+
+    val viewModel: NavScaffoldViewModel? = currentBackStackEntry?.let {
+        viewModel(viewModelStoreOwner = it)
+    }
+
+    val scrollState: State<ScrollableState?> = remember(viewModel) {
+        derivedStateOf {
+            viewModel?.timeTextScrollableState()
+        }
+    }
+
+    Scaffold(
+            modifier = modifier.fillMaxSize(),
+            timeText = {
+                timeText(Modifier.scrollAway(scrollState))
+            },
+            positionIndicator = {
+                key(currentBackStackEntry?.destination?.route) {
+                    val mode = viewModel?.positionIndicatorMode
+
+                    if (mode == NavScaffoldViewModel.PositionIndicatorMode.On) {
+                        NavPositionIndicator(viewModel)
+                    }
+                }
+            },
+            vignette = {
+                key(currentBackStackEntry?.destination?.route) {
+                    val vignettePosition = viewModel?.vignettePosition
+                    if (vignettePosition is NavScaffoldViewModel.VignetteMode.On) {
+                        Vignette(vignettePosition = vignettePosition.position)
+                    }
+                }
+            },
+    ) {
+        Box {
+            SwipeDismissableNavHost(
+                    navController = navController,
+                    startDestination = startDestination,
+                    state = state,
+            ) {
+                builder()
+            }
+
+            snackbar()
+        }
+    }
+}
+
+@Composable
+private fun NavPositionIndicator(viewModel: NavScaffoldViewModel) {
+    when (viewModel.scrollType) {
+        NavScaffoldViewModel.ScrollType.ScrollState ->
+            PositionIndicator(
+                    scrollState = viewModel.scrollableState as ScrollState,
+            )
+
+        NavScaffoldViewModel.ScrollType.ScalingLazyColumn -> {
+            PositionIndicator(
+                    scalingLazyListState = viewModel.scrollableState as ScalingLazyListState,
+            )
+        }
+
+        NavScaffoldViewModel.ScrollType.LazyList ->
+            PositionIndicator(
+                    lazyListState = viewModel.scrollableState as LazyListState,
+            )
+
+        else -> {}
+    }
+}
+
+/**
+ * Add a screen to the navigation graph featuring a ScalingLazyColumn.
+ *
+ * The scalingLazyListState must be taken from the [ScaffoldContext].
+ */
+@Deprecated(
+        "Use listComposable",
+)
+public fun NavGraphBuilder.scalingLazyColumnComposable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        scrollStateBuilder: () -> ScalingLazyListState,
+        content: @Composable (ScaffoldContext<ScalingLazyListState>) -> Unit,
+) {
+    composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val viewModel: NavScaffoldViewModel = viewModel(it)
+
+            val scrollState = viewModel.initializeScalingLazyListState(scrollStateBuilder)
+
+            content(ScaffoldContext(it, scrollState, viewModel))
+        }
+    }
+}
+
+/**
+ * Add a screen to the navigation graph featuring a ScalingLazyColumn.
+ *
+ * The [ScalingLazyColumnState] must be taken from the [ScrollableScaffoldContext].
+ */
+@ExperimentalHorologistApi
+public fun NavGraphBuilder.scrollable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        columnStateFactory: ScalingLazyColumnState.Factory =
+                ScalingLazyColumnDefaults.belowTimeText(),
+        content: @Composable (ScrollableScaffoldContext) -> Unit,
+) {
+    this@scrollable.composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val columnState = columnStateFactory.create()
+
+            val viewModel: NavScaffoldViewModel = viewModel(it)
+
+            viewModel.initializeScalingLazyListState(columnState)
+
+            content(ScrollableScaffoldContext(it, columnState, viewModel))
+        }
+    }
+}
+
+/**
+ * Add a screen to the navigation graph featuring a Scrollable item.
+ *
+ * The scrollState must be taken from the [ScaffoldContext].
+ */
+public fun NavGraphBuilder.scrollStateComposable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        scrollStateBuilder: () -> ScrollState = { ScrollState(0) },
+        content: @Composable (ScaffoldContext<ScrollState>) -> Unit,
+) {
+    composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val viewModel: NavScaffoldViewModel = viewModel(it)
+
+            val scrollState = viewModel.initializeScrollState(scrollStateBuilder)
+
+            content(ScaffoldContext(it, scrollState, viewModel))
+        }
+    }
+}
+
+/**
+ * Add a screen to the navigation graph featuring a Lazy list such as LazyColumn.
+ *
+ * The scrollState must be taken from the [ScaffoldContext].
+ */
+public fun NavGraphBuilder.lazyListComposable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        lazyListStateBuilder: () -> LazyListState = { LazyListState() },
+        content: @Composable (ScaffoldContext<LazyListState>) -> Unit,
+) {
+    composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val viewModel: NavScaffoldViewModel = viewModel(it)
+
+            val scrollState = viewModel.initializeLazyList(lazyListStateBuilder)
+
+            content(ScaffoldContext(it, scrollState, viewModel))
+        }
+    }
+}
+
+/**
+ * Add non scrolling screen to the navigation graph. The [NavBackStackEntry] and
+ * [NavScaffoldViewModel] are passed into the [content] block so that
+ * the Scaffold may be customised, such as disabling TimeText.
+ */
+@Deprecated(
+        "Use composable",
+        ReplaceWith("composable(route, arguments, deepLinks, lazyListStateBuilder, content)"),
+)
+public fun NavGraphBuilder.wearNavComposable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        content: @Composable (NavBackStackEntry, NavScaffoldViewModel) -> Unit,
+) {
+    composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val viewModel: NavScaffoldViewModel = viewModel()
+
+            content(it, viewModel)
+        }
+    }
+}
+
+/**
+ * Add non scrolling screen to the navigation graph. The [NavBackStackEntry] and
+ * [NavScaffoldViewModel] are passed into the [content] block so that
+ * the Scaffold may be customised, such as disabling TimeText.
+ */
+@ExperimentalHorologistApi
+public fun NavGraphBuilder.composable(
+        route: String,
+        arguments: List<NamedNavArgument> = emptyList(),
+        deepLinks: List<NavDeepLink> = emptyList(),
+        content: @Composable (NonScrollableScaffoldContext) -> Unit,
+) {
+    this@composable.composable(route, arguments, deepLinks) {
+        FocusedDestination {
+            val viewModel: NavScaffoldViewModel = viewModel()
+
+            content(NonScrollableScaffoldContext(it, viewModel))
+        }
+    }
+}
+
+@Composable
+internal fun FocusedDestination(content: @Composable () -> Unit) {
+    val lifecycle = LocalLifecycleOwner.current.lifecycle
+    val focused =
+            remember { mutableStateOf(lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) }
+
+    DisposableEffect(lifecycle) {
+        val listener = LifecycleEventObserver { _, _ ->
+            focused.value = lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
+        }
+        lifecycle.addObserver(listener)
+        onDispose {
+            lifecycle.removeObserver(listener)
+        }
+    }
+
+    HierarchicalFocusCoordinator(requiresFocus = { focused.value }) {
+        content()
+    }
+}
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Haptics.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Haptics.kt
new file mode 100644
index 0000000..c4af4a6
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Haptics.kt
@@ -0,0 +1,380 @@
+/*
+ * 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
+ *
+ *      https://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:OptIn(ExperimentalHorologistApi::class)
+
+package com.google.android.horologist.compose.rotaryinput
+
+import android.os.Build
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalView
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.withContext
+import kotlin.math.abs
+
+private const val DEBUG = false
+
+/**
+ * Debug logging that can be enabled.
+ */
+private inline fun debugLog(generateMsg: () -> String) {
+    if (DEBUG) {
+        println("RotaryHaptics: ${generateMsg()}")
+    }
+}
+
+/**
+ * Throttling events within specified timeframe. Only first and last events will be received.
+ * For a flow emitting elements 1 to 30, with a 100ms delay between them:
+ * ```
+ * val flow = flow {
+ *     for (i in 1..30) {
+ *         delay(100)
+ *         emit(i)
+ *     }
+ * }
+ * ```
+ * With timeframe=1000 only those integers will be received: 1, 10, 20, 30 .
+ */
+internal fun <T> Flow<T>.throttleLatest(timeframe: Long): Flow<T> =
+        flow {
+            conflate().collect {
+                emit(it)
+                delay(timeframe)
+            }
+        }
+
+/**
+ * Handles haptics for rotary usage
+ */
+@ExperimentalHorologistApi
+public interface RotaryHapticHandler {
+
+    /**
+     * Handles haptics when scroll is used
+     */
+    @ExperimentalHorologistApi
+    public fun handleScrollHaptic(scrollDelta: Float)
+
+    /**
+     * Handles haptics when scroll with snap is used
+     */
+    @ExperimentalHorologistApi
+    public fun handleSnapHaptic(scrollDelta: Float)
+}
+
+/**
+ * Default implementation of [RotaryHapticHandler]. It handles haptic feedback based
+ * on the [scrollableState], scrolled pixels and [hapticsThresholdPx].
+ * Haptic is not fired in this class, instead it's sent to [hapticsChannel]
+ * where it'll performed later.
+ *
+ * @param scrollableState Haptic performed based on this state
+ * @param hapticsChannel Channel to which haptic events will be sent
+ * @param hapticsThresholdPx A scroll threshold after which haptic is produced.
+ */
+public class DefaultRotaryHapticHandler(
+        private val scrollableState: ScrollableState,
+        private val hapticsChannel: Channel<RotaryHapticsType>,
+        private val hapticsThresholdPx: Long = 50,
+) : RotaryHapticHandler {
+
+    private var overscrollHapticTriggered = false
+    private var currScrollPosition = 0f
+    private var prevHapticsPosition = 0f
+
+    override fun handleScrollHaptic(scrollDelta: Float) {
+        if ((scrollDelta > 0 && !scrollableState.canScrollForward) ||
+                (scrollDelta < 0 && !scrollableState.canScrollBackward)
+        ) {
+            if (!overscrollHapticTriggered) {
+                trySendHaptic(RotaryHapticsType.ScrollLimit)
+                overscrollHapticTriggered = true
+            }
+        } else {
+            overscrollHapticTriggered = false
+            currScrollPosition += scrollDelta
+            val diff = abs(currScrollPosition - prevHapticsPosition)
+
+            if (diff >= hapticsThresholdPx) {
+                trySendHaptic(RotaryHapticsType.ScrollTick)
+                prevHapticsPosition = currScrollPosition
+            }
+        }
+    }
+
+    override fun handleSnapHaptic(scrollDelta: Float) {
+        if ((scrollDelta > 0 && !scrollableState.canScrollForward) ||
+                (scrollDelta < 0 && !scrollableState.canScrollBackward)
+        ) {
+            if (!overscrollHapticTriggered) {
+                trySendHaptic(RotaryHapticsType.ScrollLimit)
+                overscrollHapticTriggered = true
+            }
+        } else {
+            overscrollHapticTriggered = false
+            trySendHaptic(RotaryHapticsType.ScrollItemFocus)
+        }
+    }
+
+    private fun trySendHaptic(rotaryHapticsType: RotaryHapticsType) {
+        // Ok to ignore the ChannelResult because we default to capacity = 2 and DROP_OLDEST
+        @Suppress("UNUSED_VARIABLE")
+        val unused = hapticsChannel.trySend(rotaryHapticsType)
+    }
+}
+
+/**
+ * Interface for Rotary haptic feedback
+ */
+@ExperimentalHorologistApi
+public interface RotaryHapticFeedback {
+    @ExperimentalHorologistApi
+    public fun performHapticFeedback(type: RotaryHapticsType)
+}
+
+/**
+ * Rotary haptic types
+ */
+@ExperimentalHorologistApi
+@JvmInline
+public value class RotaryHapticsType(private val type: Int) {
+    public companion object {
+        /**
+         * A scroll ticking haptic. Similar to texture haptic - performed each time when
+         * a scrollable content is scrolled by a certain distance
+         */
+        @ExperimentalHorologistApi
+        public val ScrollTick: RotaryHapticsType = RotaryHapticsType(1)
+
+        /**
+         * An item focus (snap) haptic. Performed when a scrollable content is snapped
+         * to a specific item.
+         */
+        @ExperimentalHorologistApi
+        public val ScrollItemFocus: RotaryHapticsType = RotaryHapticsType(2)
+
+        /**
+         * A limit(overscroll) haptic. Performed when a list reaches the limit
+         * (start or end) and can't scroll further
+         */
+        @ExperimentalHorologistApi
+        public val ScrollLimit: RotaryHapticsType = RotaryHapticsType(3)
+    }
+}
+
+/**
+ * Remember disabled haptics handler
+ */
+@ExperimentalHorologistApi
+@Composable
+public fun rememberDisabledHaptic(): RotaryHapticHandler = remember {
+    object : RotaryHapticHandler {
+
+        override fun handleScrollHaptic(scrollDelta: Float) {
+            // Do nothing
+        }
+
+        override fun handleSnapHaptic(scrollDelta: Float) {
+            // Do nothing
+        }
+    }
+}
+
+/**
+ * Remember rotary haptic handler.
+ * @param scrollableState A scrollableState, used to determine whether the end of the scrollable
+ * was reached or not.
+ * @param throttleThresholdMs Throttling events within specified timeframe.
+ * Only first and last events will be received. Check [throttleLatest] for more info.
+ * @param hapticsThresholdPx A scroll threshold after which haptic is produced.
+ * @param hapticsChannel Channel to which haptic events will be sent
+ * @param rotaryHaptics Interface for Rotary haptic feedback which performs haptics
+ */
+@ExperimentalHorologistApi
+@Composable
+public fun rememberRotaryHapticHandler(
+        scrollableState: ScrollableState,
+        throttleThresholdMs: Long = 30,
+        hapticsThresholdPx: Long = 50,
+        hapticsChannel: Channel<RotaryHapticsType> = rememberHapticChannel(),
+        rotaryHaptics: RotaryHapticFeedback = rememberDefaultRotaryHapticFeedback(),
+): RotaryHapticHandler {
+    return remember(scrollableState, hapticsChannel, rotaryHaptics) {
+        DefaultRotaryHapticHandler(scrollableState, hapticsChannel, hapticsThresholdPx)
+    }.apply {
+        LaunchedEffect(hapticsChannel) {
+            hapticsChannel.receiveAsFlow()
+                    .throttleLatest(throttleThresholdMs)
+                    .collect { hapticType ->
+                        // 'withContext' launches performHapticFeedback in a separate thread,
+                        // as otherwise it produces a visible lag (b/219776664)
+                        val currentTime = System.currentTimeMillis()
+                        debugLog { "Haptics started" }
+                        withContext(Dispatchers.Default) {
+                            debugLog {
+                                "Performing haptics, delay: " +
+                                        "${System.currentTimeMillis() - currentTime}"
+                            }
+                            rotaryHaptics.performHapticFeedback(hapticType)
+                        }
+                    }
+        }
+    }
+}
+
+@Composable
+private fun rememberHapticChannel() =
+        remember {
+            Channel<RotaryHapticsType>(
+                    capacity = 2,
+                    onBufferOverflow = BufferOverflow.DROP_OLDEST,
+            )
+        }
+
+@ExperimentalHorologistApi
+@Composable
+public fun rememberDefaultRotaryHapticFeedback(): RotaryHapticFeedback =
+        LocalView.current.let { view -> remember { findDeviceSpecificHapticFeedback(view) } }
+
+internal fun findDeviceSpecificHapticFeedback(view: View): RotaryHapticFeedback =
+        if (isGooglePixelWatch()) {
+            PixelWatchRotaryHapticFeedback(view)
+        } else if (isGalaxyWatchClassic()) {
+            GalaxyWatchClassicHapticFeedback(view)
+        } else {
+            DefaultRotaryHapticFeedback(view)
+        }
+
+/**
+ * Default Rotary implementation for [RotaryHapticFeedback]
+ */
+@ExperimentalHorologistApi
+public class DefaultRotaryHapticFeedback(private val view: View) : RotaryHapticFeedback {
+
+    @ExperimentalHorologistApi
+    override fun performHapticFeedback(
+            type: RotaryHapticsType,
+    ) {
+        when (type) {
+            RotaryHapticsType.ScrollItemFocus -> {
+                view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+            }
+
+            RotaryHapticsType.ScrollTick -> {
+                view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
+            }
+
+            RotaryHapticsType.ScrollLimit -> {
+                view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+            }
+        }
+    }
+}
+
+/**
+ * Implementation of [RotaryHapticFeedback] for Pixel Watch
+ */
+@ExperimentalHorologistApi
+private class PixelWatchRotaryHapticFeedback(private val view: View) : RotaryHapticFeedback {
+
+    @ExperimentalHorologistApi
+    override fun performHapticFeedback(
+            type: RotaryHapticsType,
+    ) {
+        when (type) {
+            RotaryHapticsType.ScrollItemFocus -> {
+                view.performHapticFeedback(
+                        if (Build.VERSION.SDK_INT >= 33) {
+                            ROTARY_SCROLL_ITEM_FOCUS
+                        } else {
+                            WEAR_SCROLL_ITEM_FOCUS
+                        },
+                )
+            }
+
+            RotaryHapticsType.ScrollTick -> {
+                view.performHapticFeedback(
+                        if (Build.VERSION.SDK_INT >= 33) ROTARY_SCROLL_TICK else WEAR_SCROLL_TICK,
+                )
+            }
+
+            RotaryHapticsType.ScrollLimit -> {
+                view.performHapticFeedback(
+                        if (Build.VERSION.SDK_INT >= 33) ROTARY_SCROLL_LIMIT else WEAR_SCROLL_LIMIT,
+                )
+            }
+        }
+    }
+
+    private companion object {
+        // Hidden constants from HapticFeedbackConstants.java specific for Pixel Watch
+        // API 33
+        public const val ROTARY_SCROLL_TICK: Int = 18
+        public const val ROTARY_SCROLL_ITEM_FOCUS: Int = 19
+        public const val ROTARY_SCROLL_LIMIT: Int = 20
+
+        // API 30
+        public const val WEAR_SCROLL_TICK: Int = 10002
+        public const val WEAR_SCROLL_ITEM_FOCUS: Int = 10003
+        public const val WEAR_SCROLL_LIMIT: Int = 10003
+    }
+}
+
+/**
+ * Implementation of [RotaryHapticFeedback] for Galaxy Watch 4 Classic
+ */
+@ExperimentalHorologistApi
+private class GalaxyWatchClassicHapticFeedback(private val view: View) : RotaryHapticFeedback {
+
+    @ExperimentalHorologistApi
+    override fun performHapticFeedback(
+            type: RotaryHapticsType,
+    ) {
+        when (type) {
+            RotaryHapticsType.ScrollItemFocus -> {
+                // No haptic for scroll snap ( we have physical bezel)
+            }
+
+            RotaryHapticsType.ScrollTick -> {
+                // No haptic for scroll tick ( we have physical bezel)
+            }
+
+            RotaryHapticsType.ScrollLimit -> {
+                view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+            }
+        }
+    }
+}
+
+private fun isGalaxyWatchClassic(): Boolean =
+        Build.MODEL.matches("SM-R8[89]5.".toRegex())
+
+private fun isGooglePixelWatch(): Boolean =
+        Build.MODEL.startsWith("Google Pixel Watch")
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Rotary.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Rotary.kt
new file mode 100644
index 0000000..3ca16c1
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/Rotary.kt
@@ -0,0 +1,1301 @@
+/*
+ * 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
+ *
+ *      https://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:OptIn(ExperimentalHorologistApi::class)
+
+package com.google.android.horologist.compose.rotaryinput
+
+import android.view.ViewConfiguration
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.CubicBezierEasing
+import androidx.compose.animation.core.Easing
+import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.SpringSpec
+import androidx.compose.animation.core.animateTo
+import androidx.compose.animation.core.copy
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollableDefaults
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.input.rotary.onRotaryScrollEvent
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.util.fastSumBy
+import androidx.compose.ui.util.lerp
+import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.rememberActiveFocusRequester
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.transformLatest
+import kotlin.math.abs
+import kotlin.math.absoluteValue
+import kotlin.math.sign
+
+private const val DEBUG = false
+
+/**
+ * Debug logging that can be enabled.
+ */
+private inline fun debugLog(generateMsg: () -> String) {
+    if (DEBUG) {
+        println("RotaryScroll: ${generateMsg()}")
+    }
+}
+
+/**
+ * A modifier which connects rotary events with scrollable.
+ * This modifier supports fling.
+ *
+ *  Fling algorithm:
+ * - A scroll with RSB/ Bezel happens.
+ * - If this is a first rotary event after the threshold ( by default 200ms), a new scroll
+ * session starts by resetting all necessary parameters
+ * - A delta value is added into VelocityTracker and a new speed is calculated.
+ * - If the current speed is bigger than the previous one,  this value is remembered as
+ * a latest fling speed with a timestamp
+ * - After each scroll event a fling countdown starts ( by default 70ms) which
+ * resets if new scroll event is received
+ * - If fling countdown is finished - it means that the finger was probably raised from RSB, there will be no other events and probably
+ * this is the last event during this session. After it a fling is triggered.
+ * - Fling is stopped when a new scroll event happens
+ *
+ * The screen containing the scrollable item should request the focus
+ * by calling [requestFocus] method
+ *
+ * ```
+ * LaunchedEffect(Unit) {
+ *   focusRequester.requestFocus()
+ * }
+ * ```
+ * @param focusRequester Requests the focus for rotary input
+ * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
+ * @param flingBehavior Logic describing fling behavior.
+ * @param rotaryHaptics Class which will handle haptic feedback
+ * @param reverseDirection Reverse the direction of scrolling. Should be aligned with
+ * Scrollable `reverseDirection` parameter
+ */
+@ExperimentalHorologistApi
+@Suppress("ComposableModifierFactory")
+@Deprecated(
+        "Use rotaryWithScroll instead",
+        ReplaceWith(
+                "this.rotaryWithScroll(scrollableState, focusRequester, " +
+                        "flingBehavior, rotaryHaptics, reverseDirection)",
+        ),
+)
+@Composable
+public fun Modifier.rotaryWithFling(
+        focusRequester: FocusRequester,
+        scrollableState: ScrollableState,
+        flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+        rotaryHaptics: RotaryHapticHandler = rememberRotaryHapticHandler(scrollableState),
+        reverseDirection: Boolean = false,
+): Modifier = rotaryHandler(
+        rotaryScrollHandler = RotaryDefaults.rememberFlingHandler(scrollableState, flingBehavior),
+        reverseDirection = reverseDirection,
+        rotaryHaptics = rotaryHaptics,
+)
+        .focusRequester(focusRequester)
+        .focusable()
+
+/**
+ * A modifier which connects rotary events with scrollable.
+ * This modifier supports scroll with fling.
+ *
+ * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
+ * @param focusRequester Requests the focus for rotary input.
+ * By default comes from [rememberActiveFocusRequester],
+ * which is used with [HierarchicalFocusCoordinator]
+ * @param flingBehavior Logic describing fling behavior. If null fling will not happen.
+ * @param rotaryHaptics Class which will handle haptic feedback
+ * @param reverseDirection Reverse the direction of scrolling. Should be aligned with
+ * Scrollable `reverseDirection` parameter
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+@ExperimentalHorologistApi
+@Suppress("ComposableModifierFactory")
+@Composable
+public fun Modifier.rotaryWithScroll(
+        scrollableState: ScrollableState,
+        focusRequester: FocusRequester = rememberActiveFocusRequester(),
+        flingBehavior: FlingBehavior? = ScrollableDefaults.flingBehavior(),
+        rotaryHaptics: RotaryHapticHandler = rememberRotaryHapticHandler(scrollableState),
+        reverseDirection: Boolean = false,
+): Modifier = rotaryHandler(
+        rotaryScrollHandler = RotaryDefaults.rememberFlingHandler(scrollableState, flingBehavior),
+        reverseDirection = reverseDirection,
+        rotaryHaptics = rotaryHaptics,
+)
+        .focusRequester(focusRequester)
+        .focusable()
+
+/**
+ * A modifier which connects rotary events with scrollable.
+ * This modifier supports snap.
+ *
+ * @param focusRequester Requests the focus for rotary input.
+ * By default comes from [rememberActiveFocusRequester],
+ * which is used with [HierarchicalFocusCoordinator]
+ * @param rotaryScrollAdapter A connection between scrollable objects and rotary events
+ * @param rotaryHaptics Class which will handle haptic feedback
+ * @param reverseDirection Reverse the direction of scrolling. Should be aligned with
+ * Scrollable `reverseDirection` parameter
+ */
+@OptIn(ExperimentalWearFoundationApi::class)
+@ExperimentalHorologistApi
+@Suppress("ComposableModifierFactory")
+@Composable
+public fun Modifier.rotaryWithSnap(
+        rotaryScrollAdapter: RotaryScrollAdapter,
+        focusRequester: FocusRequester = rememberActiveFocusRequester(),
+        snapParameters: SnapParameters = RotaryDefaults.snapParametersDefault(),
+        rotaryHaptics: RotaryHapticHandler =
+                rememberRotaryHapticHandler(rotaryScrollAdapter.scrollableState),
+        reverseDirection: Boolean = false,
+): Modifier = rotaryHandler(
+        rotaryScrollHandler =
+            RotaryDefaults.rememberSnapHandler(rotaryScrollAdapter, snapParameters),
+        reverseDirection = reverseDirection,
+        rotaryHaptics = rotaryHaptics,
+)
+        .focusRequester(focusRequester)
+        .focusable()
+
+/**
+ * An extension function for creating [RotaryScrollAdapter] from [ScalingLazyListState]
+ */
+@ExperimentalHorologistApi
+public fun ScalingLazyListState.toRotaryScrollAdapter(): RotaryScrollAdapter =
+        ScalingLazyColumnRotaryScrollAdapter(this)
+
+/**
+ * An implementation of rotary scroll adapter for [ScalingLazyColumn]
+ */
+@ExperimentalHorologistApi
+public class ScalingLazyColumnRotaryScrollAdapter(
+        override val scrollableState: ScalingLazyListState,
+) : RotaryScrollAdapter {
+
+    /**
+     * Calculates an average height of an item by taking an average from visible items height.
+     */
+    override fun averageItemSize(): Float {
+        val visibleItems = scrollableState.layoutInfo.visibleItemsInfo
+        return (visibleItems.fastSumBy { it.unadjustedSize } / visibleItems.size).toFloat()
+    }
+
+    /**
+     * Current (centred) item index
+     */
+    override fun currentItemIndex(): Int = scrollableState.centerItemIndex
+
+    /**
+     * An offset from the item centre
+     */
+    override fun currentItemOffset(): Float = scrollableState.centerItemScrollOffset.toFloat()
+
+    /**
+     * The total count of items in ScalingLazyColumn
+     */
+    override fun totalItemsCount(): Int = scrollableState.layoutInfo.totalItemsCount
+}
+
+/**
+ * An adapter which connects scrollableState to Rotary
+ */
+@ExperimentalHorologistApi
+public interface RotaryScrollAdapter {
+
+    /**
+     * A scrollable state. Used for performing scroll when Rotary events received
+     */
+    @ExperimentalHorologistApi
+    public val scrollableState: ScrollableState
+
+    /**
+     * Average size of an item. Used for estimating the scrollable distance
+     */
+    @ExperimentalHorologistApi
+    public fun averageItemSize(): Float
+
+    /**
+     * A current item index. Used for scrolling
+     */
+    @ExperimentalHorologistApi
+    public fun currentItemIndex(): Int
+
+    /**
+     * An offset from the centre or the border of the current item.
+     */
+    @ExperimentalHorologistApi
+    public fun currentItemOffset(): Float
+
+    /**
+     * The total count of items in [scrollableState]
+     */
+    @ExperimentalHorologistApi
+    public fun totalItemsCount(): Int
+}
+
+/**
+ * Defaults for rotary modifiers
+ */
+@ExperimentalHorologistApi
+public object RotaryDefaults {
+
+    /**
+     * Handles scroll with fling.
+     * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
+     * @param flingBehavior Logic describing Fling behavior. If null - fling will not happen
+     * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb)
+     */
+    @ExperimentalHorologistApi
+    @Composable
+    public fun rememberFlingHandler(
+            scrollableState: ScrollableState,
+            flingBehavior: FlingBehavior? = null,
+            isLowRes: Boolean = isLowResInput(),
+    ): RotaryScrollHandler {
+        val viewConfiguration = ViewConfiguration.get(LocalContext.current)
+
+        return remember(scrollableState, flingBehavior, isLowRes) {
+            debugLog { "isLowRes : $isLowRes" }
+            fun rotaryFlingBehavior() = flingBehavior?.run {
+                DefaultRotaryFlingBehavior(
+                        scrollableState,
+                        flingBehavior,
+                        viewConfiguration,
+                        flingTimeframe =
+                            if (isLowRes) lowResFlingTimeframe else highResFlingTimeframe,
+                )
+            }
+
+            fun scrollBehavior() = AnimationScrollBehavior(scrollableState)
+
+            if (isLowRes) {
+                LowResRotaryScrollHandler(
+                        rotaryFlingBehaviorFactory = { rotaryFlingBehavior() },
+                        scrollBehaviorFactory = { scrollBehavior() },
+                )
+            } else {
+                HighResRotaryScrollHandler(
+                        rotaryFlingBehaviorFactory = { rotaryFlingBehavior() },
+                        scrollBehaviorFactory = { scrollBehavior() },
+                )
+            }
+        }
+    }
+
+    /**
+     * Handles scroll with snap
+     * @param rotaryScrollAdapter A connection between scrollable objects and rotary events
+     * @param snapParameters Snap parameters
+     */
+    @ExperimentalHorologistApi
+    @Composable
+    public fun rememberSnapHandler(
+            rotaryScrollAdapter: RotaryScrollAdapter,
+            snapParameters: SnapParameters = snapParametersDefault(),
+            isLowRes: Boolean = isLowResInput(),
+    ): RotaryScrollHandler {
+        return remember(rotaryScrollAdapter, snapParameters) {
+            if (isLowRes) {
+                LowResSnapHandler(
+                        snapBehaviourFactory = {
+                            DefaultSnapBehavior(rotaryScrollAdapter, snapParameters)
+                        },
+                )
+            } else {
+                HighResSnapHandler(
+                        resistanceFactor = snapParameters.resistanceFactor,
+                        thresholdBehaviorFactory = {
+                            ThresholdBehavior(
+                                    rotaryScrollAdapter,
+                                    snapParameters.thresholdDivider,
+                            )
+                        },
+                        snapBehaviourFactory = {
+                            DefaultSnapBehavior(rotaryScrollAdapter, snapParameters)
+                        },
+                        scrollBehaviourFactory = {
+                            AnimationScrollBehavior(rotaryScrollAdapter.scrollableState)
+                        },
+                )
+            }
+        }
+    }
+
+    /**
+     * Returns default [SnapParameters]
+     */
+    @ExperimentalHorologistApi
+    public fun snapParametersDefault(): SnapParameters =
+            SnapParameters(
+                    snapOffset = 0,
+                    thresholdDivider = 1.5f,
+                    resistanceFactor = 3f,
+            )
+
+    /**
+     * Returns whether the input is Low-res (a bezel) or high-res(a crown/rsb).
+     */
+    @ExperimentalHorologistApi
+    @Composable
+    public fun isLowResInput(): Boolean = LocalContext.current.packageManager
+            .hasSystemFeature("android.hardware.rotaryencoder.lowres")
+
+    private val lowResFlingTimeframe: Long = 100L
+    private val highResFlingTimeframe: Long = 30L
+}
+
+/**
+ * Parameters used for snapping
+ *
+ * @param snapOffset an optional offset to be applied when snapping the item. After the snap the
+ * snapped items offset will be [snapOffset].
+ */
+public class SnapParameters(
+        public val snapOffset: Int,
+        public val thresholdDivider: Float,
+        public val resistanceFactor: Float,
+) {
+    /**
+     * Returns a snapping offset in [Dp]
+     */
+    @Composable
+    public fun snapOffsetDp(): Dp {
+        return with(LocalDensity.current) {
+            snapOffset.toDp()
+        }
+    }
+}
+
+/**
+ * An interface for handling scroll events
+ */
+@ExperimentalHorologistApi
+public interface RotaryScrollHandler {
+    /**
+     * Handles scrolling events
+     * @param coroutineScope A scope for performing async actions
+     * @param event A scrollable event from rotary input, containing scrollable delta and timestamp
+     * @param rotaryHaptics
+     */
+    @ExperimentalHorologistApi
+    public suspend fun handleScrollEvent(
+            coroutineScope: CoroutineScope,
+            event: TimestampedDelta,
+            rotaryHaptics: RotaryHapticHandler,
+    )
+}
+
+/**
+ * An interface for scrolling behavior
+ */
+@ExperimentalHorologistApi
+public interface RotaryScrollBehavior {
+    /**
+     * Handles scroll event to [targetValue]
+     */
+    @ExperimentalHorologistApi
+    public suspend fun handleEvent(targetValue: Float)
+}
+
+/**
+ * Default implementation of [RotaryFlingBehavior]
+ */
+@ExperimentalHorologistApi
+public class DefaultRotaryFlingBehavior(
+        private val scrollableState: ScrollableState,
+        private val flingBehavior: FlingBehavior,
+        viewConfiguration: ViewConfiguration,
+        private val flingTimeframe: Long,
+) : RotaryFlingBehavior {
+
+    // A time range during which the fling is valid.
+    // For simplicity it's twice as long as [flingTimeframe]
+    private val timeRangeToFling = flingTimeframe * 2
+
+    //  A default fling factor for making fling slower
+    private val flingScaleFactor = 0.7f
+
+    private var previousVelocity = 0f
+
+    private val rotaryVelocityTracker = RotaryVelocityTracker()
+
+    private val minFlingSpeed = viewConfiguration.scaledMinimumFlingVelocity.toFloat()
+    private val maxFlingSpeed = viewConfiguration.scaledMaximumFlingVelocity.toFloat()
+    private var latestEventTimestamp: Long = 0
+
+    private var flingVelocity: Float = 0f
+    private var flingTimestamp: Long = 0
+
+    @ExperimentalHorologistApi
+    override fun startFlingTracking(timestamp: Long) {
+        rotaryVelocityTracker.start(timestamp)
+        latestEventTimestamp = timestamp
+        previousVelocity = 0f
+    }
+
+    @ExperimentalHorologistApi
+    override fun observeEvent(timestamp: Long, delta: Float) {
+        rotaryVelocityTracker.move(timestamp, delta)
+        latestEventTimestamp = timestamp
+    }
+
+    @ExperimentalHorologistApi
+    override suspend fun trackFling(beforeFling: () -> Unit) {
+        val currentVelocity = rotaryVelocityTracker.velocity
+        debugLog { "currentVelocity: $currentVelocity" }
+
+        if (abs(currentVelocity) >= abs(previousVelocity)) {
+            flingTimestamp = latestEventTimestamp
+            flingVelocity = currentVelocity * flingScaleFactor
+        }
+        previousVelocity = currentVelocity
+
+        // Waiting for a fixed amount of time before checking the fling
+        delay(flingTimeframe)
+
+        // For making a fling 2 criteria should be met:
+        // 1) no more than
+        // `rangeToFling` ms should pass between last fling detection
+        // and the time of last motion event
+        // 2) flingVelocity should exceed the minFlingSpeed
+        debugLog {
+            "Check fling:  flingVelocity: $flingVelocity " +
+                    "minFlingSpeed: $minFlingSpeed, maxFlingSpeed: $maxFlingSpeed"
+        }
+        if (latestEventTimestamp - flingTimestamp < timeRangeToFling &&
+                abs(flingVelocity) > minFlingSpeed
+        ) {
+            // Stops scrollAnimationCoroutine because a fling will be performed
+            beforeFling()
+            val velocity = flingVelocity.coerceIn(-maxFlingSpeed, maxFlingSpeed)
+            scrollableState.scroll(MutatePriority.UserInput) {
+                with(flingBehavior) {
+                    debugLog { "Flinging with velocity $velocity" }
+                    performFling(velocity)
+                }
+            }
+        }
+    }
+}
+
+/**
+ * An interface for flinging with rotary
+ */
+@ExperimentalHorologistApi
+public interface RotaryFlingBehavior {
+
+    /**
+     * Observing new event within a fling tracking session with new timestamp and delta
+     */
+    @ExperimentalHorologistApi
+    public fun observeEvent(timestamp: Long, delta: Float)
+
+    /**
+     * Performing fling if necessary and calling [beforeFling] lambda before it is triggered
+     */
+    @ExperimentalHorologistApi
+    public suspend fun trackFling(beforeFling: () -> Unit)
+
+    /**
+     * Starts a new fling tracking session
+     * with specified timestamp
+     */
+    @ExperimentalHorologistApi
+    public fun startFlingTracking(timestamp: Long)
+}
+
+/**
+ * An interface for snapping with rotary
+ */
+@ExperimentalHorologistApi
+public interface RotarySnapBehavior {
+
+    /**
+     * Preparing snapping. This method should be called before [snapToTargetItem] is called.
+     *
+     * Snapping is done for current + [moveForElements] items.
+     *
+     * If [sequentialSnap] is true, items are summed up together.
+     * For example, if [prepareSnapForItems] is called with
+     * [moveForElements] = 2, 3, 5 -> then the snapping will happen to current + 10 items
+     *
+     * If [sequentialSnap] is false, then [moveForElements] are not summed up together.
+     */
+    public fun prepareSnapForItems(moveForElements: Int, sequentialSnap: Boolean)
+
+    /**
+     * Performs snapping to the closest item.
+     */
+    public suspend fun snapToClosestItem()
+
+    /**
+     * Returns true if top edge was reached
+     */
+    public fun topEdgeReached(): Boolean
+
+    /**
+     * Returns true if bottom edge was reached
+     */
+    public fun bottomEdgeReached(): Boolean
+
+    /**
+     * Performs snapping to the specified in [prepareSnapForItems] element
+     */
+    public suspend fun snapToTargetItem()
+}
+
+/**
+ * A rotary event object which contains a [timestamp] of the rotary event and a scrolled [delta].
+ */
+@ExperimentalHorologistApi
+public data class TimestampedDelta(val timestamp: Long, val delta: Float)
+
+/** Animation implementation of [RotaryScrollBehavior].
+ * This class does a smooth animation when the scroll by N pixels is done.
+ * This animation works well on Rsb(high-res) and Bezel(low-res) devices.
+ */
+@ExperimentalHorologistApi
+public class AnimationScrollBehavior(
+        private val scrollableState: ScrollableState,
+) : RotaryScrollBehavior {
+    private var sequentialAnimation = false
+    private var scrollAnimation = AnimationState(0f)
+    private var prevPosition = 0f
+
+    @ExperimentalHorologistApi
+    override suspend fun handleEvent(targetValue: Float) {
+        scrollableState.scroll(MutatePriority.UserInput) {
+            debugLog { "ScrollAnimation value before start: ${scrollAnimation.value}" }
+
+            scrollAnimation.animateTo(
+                    targetValue,
+                    animationSpec = spring(),
+                    sequentialAnimation = sequentialAnimation,
+            ) {
+                val delta = value - prevPosition
+                debugLog { "Animated by $delta, value: $value" }
+                scrollBy(delta)
+                prevPosition = value
+                sequentialAnimation = value != this.targetValue
+            }
+        }
+    }
+}
+
+/**
+ * An animated implementation of [RotarySnapBehavior]. Uses animateScrollToItem
+ * method for snapping to the Nth item
+ */
+@ExperimentalHorologistApi
+public class DefaultSnapBehavior(
+        private val rotaryScrollAdapter: RotaryScrollAdapter,
+        private val snapParameters: SnapParameters,
+) : RotarySnapBehavior {
+    private var snapTarget: Int = rotaryScrollAdapter.currentItemIndex()
+    private var sequentialSnap: Boolean = false
+
+    private var anim = AnimationState(0f)
+    private var expectedDistance = 0f
+
+    private val defaultStiffness = 200f
+    private var snapTargetUpdated = true
+
+    @ExperimentalHorologistApi
+    override fun prepareSnapForItems(moveForElements: Int, sequentialSnap: Boolean) {
+        this.sequentialSnap = sequentialSnap
+        if (sequentialSnap) {
+            snapTarget += moveForElements
+        } else {
+            snapTarget = rotaryScrollAdapter.currentItemIndex() + moveForElements
+        }
+        snapTargetUpdated = true
+        snapTarget = snapTarget.coerceIn(0 until rotaryScrollAdapter.totalItemsCount())
+    }
+
+    override suspend fun snapToClosestItem() {
+        // Snapping to the closest item by using performFling method with 0 speed
+        rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+            debugLog { "snap to closest item" }
+            var prevPosition = 0f
+            AnimationState(0f).animateTo(
+                    targetValue = -rotaryScrollAdapter.currentItemOffset(),
+                    animationSpec = tween(durationMillis = 100, easing = FastOutSlowInEasing),
+            ) {
+                val animDelta = value - prevPosition
+                scrollBy(animDelta)
+                prevPosition = value
+            }
+            snapTarget = rotaryScrollAdapter.currentItemIndex()
+        }
+    }
+
+    override fun topEdgeReached(): Boolean = snapTarget <= 0
+
+    override fun bottomEdgeReached(): Boolean =
+            snapTarget >= rotaryScrollAdapter.totalItemsCount() - 1
+
+    override suspend fun snapToTargetItem() {
+        if (sequentialSnap) {
+            anim = anim.copy(0f)
+        } else {
+            anim = AnimationState(0f)
+        }
+        rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
+            // If snapTargetUpdated is true - then the target was updated so we
+            // need to do snap again
+            while (snapTargetUpdated) {
+                snapTargetUpdated = false
+                var latestCenterItem: Int
+                var continueFirstScroll = true
+                debugLog { "snapTarget $snapTarget" }
+                while (continueFirstScroll) {
+                    latestCenterItem = rotaryScrollAdapter.currentItemIndex()
+                    anim = anim.copy(0f)
+                    expectedDistance = expectedDistanceTo(snapTarget, snapParameters.snapOffset)
+                    debugLog {
+                        "expectedDistance = $expectedDistance, " +
+                                "scrollableState.centerItemScrollOffset " +
+                                "${rotaryScrollAdapter.currentItemOffset()}"
+                    }
+                    continueFirstScroll = false
+                    var prevPosition = 0f
+
+                    anim.animateTo(
+                            expectedDistance,
+                            animationSpec = SpringSpec(
+                                    stiffness = defaultStiffness,
+                                    visibilityThreshold = 0.1f,
+                            ),
+                            sequentialAnimation = (anim.velocity != 0f),
+                    ) {
+                        val animDelta = value - prevPosition
+                        debugLog {
+                            "First animation, value:$value, velocity:$velocity, " +
+                                    "animDelta:$animDelta"
+                        }
+
+                        // Exit animation if snap target was updated
+                        if (snapTargetUpdated) cancelAnimation()
+
+                        scrollBy(animDelta)
+                        prevPosition = value
+
+                        if (latestCenterItem != rotaryScrollAdapter.currentItemIndex()) {
+                            continueFirstScroll = true
+                            cancelAnimation()
+                            return@animateTo
+                        }
+
+                        debugLog { "centerItemIndex =  ${rotaryScrollAdapter.currentItemIndex()}" }
+                        if (rotaryScrollAdapter.currentItemIndex() == snapTarget) {
+                            debugLog { "Target is visible. Cancelling first animation" }
+                            debugLog {
+                                "scrollableState.centerItemScrollOffset " +
+                                        "${rotaryScrollAdapter.currentItemOffset()}"
+                            }
+                            expectedDistance = -rotaryScrollAdapter.currentItemOffset()
+                            continueFirstScroll = false
+                            cancelAnimation()
+                            return@animateTo
+                        }
+                    }
+                }
+                // Exit animation if snap target was updated
+                if (snapTargetUpdated) continue
+
+                anim = anim.copy(0f)
+                var prevPosition = 0f
+                anim.animateTo(
+                        expectedDistance,
+                        animationSpec = SpringSpec(
+                                stiffness = defaultStiffness,
+                                visibilityThreshold = 0.1f,
+                        ),
+                        sequentialAnimation = (anim.velocity != 0f),
+                ) {
+                    // Exit animation if snap target was updated
+                    if (snapTargetUpdated) cancelAnimation()
+
+                    val animDelta = value - prevPosition
+                    debugLog { "Final animation. velocity:$velocity, animDelta:$animDelta" }
+                    scrollBy(animDelta)
+                    prevPosition = value
+                }
+            }
+        }
+    }
+
+    private fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
+        val averageSize = rotaryScrollAdapter.averageItemSize()
+        val indexesDiff = index - rotaryScrollAdapter.currentItemIndex()
+        debugLog { "Average size $averageSize" }
+        return (averageSize * indexesDiff) +
+                targetScrollOffset - rotaryScrollAdapter.currentItemOffset()
+    }
+}
+
+/**
+ * A modifier which handles rotary events.
+ * It accepts ScrollHandler as the input - a class where main logic about how
+ * scroll should be handled is lying
+ */
+@ExperimentalHorologistApi
+@OptIn(ExperimentalComposeUiApi::class)
+public fun Modifier.rotaryHandler(
+        rotaryScrollHandler: RotaryScrollHandler,
+        // TODO: batching causes additional delays. Return once it's clear that
+        //  we will use it
+        /* batchTimeframe: Long = 0L,*/
+        reverseDirection: Boolean,
+        rotaryHaptics: RotaryHapticHandler,
+): Modifier = composed {
+    val channel = rememberTimestampChannel()
+    val eventsFlow = remember(channel) { channel.receiveAsFlow() }
+
+    composed {
+        LaunchedEffect(eventsFlow) {
+            eventsFlow
+                    // TODO: batching causes additional delays. Return once it's clear that
+                    //  we will use it
+                    // Do we really need to do this on this level?
+//                .batchRequestsWithinTimeframe(batchTimeframe)
+                    .collectLatest {
+                        debugLog {
+                            "Scroll event received: " +
+                                    "delta:${it.delta}, timestamp:${it.timestamp}"
+                        }
+                        rotaryScrollHandler.handleScrollEvent(this, it, rotaryHaptics)
+                    }
+        }
+        this
+                .onRotaryScrollEvent {
+                    // Okay to ignore the ChannelResult returned from trySend because it is conflated
+                    // (see rememberTimestampChannel()).
+                    @Suppress("UNUSED_VARIABLE")
+                    val unused = channel.trySend(
+                            TimestampedDelta(
+                                    it.uptimeMillis,
+                                    it.verticalScrollPixels * if (reverseDirection) -1f else 1f,
+                            ),
+                    )
+                    true
+                }
+    }
+}
+
+/**
+ * Batching requests for scrolling events. This function combines all events together
+ * (except first) within specified timeframe. Should help with performance on high-res devices.
+ */
+@ExperimentalHorologistApi
+@OptIn(ExperimentalCoroutinesApi::class)
+public fun Flow<TimestampedDelta>.batchRequestsWithinTimeframe(
+        timeframe: Long
+): Flow<TimestampedDelta> {
+    var delta = 0f
+    var lastTimestamp = -timeframe
+    return if (timeframe == 0L) {
+        this
+    } else {
+        this.transformLatest {
+            delta += it.delta
+            debugLog { "Batching requests. delta:$delta" }
+            if (lastTimestamp + timeframe <= it.timestamp) {
+                lastTimestamp = it.timestamp
+                debugLog { "No events before, delta= $delta" }
+                emit(TimestampedDelta(it.timestamp, delta))
+            } else {
+                delay(timeframe)
+                debugLog { "After delay, delta= $delta" }
+                if (delta > 0f) {
+                    emit(TimestampedDelta(it.timestamp, delta))
+                }
+            }
+            delta = 0f
+        }
+    }
+}
+
+/**
+ * A scroll handler for RSB(high-res) without snapping and with or without fling
+ * A list is scrolled by the number of pixels received from the rotary device.
+ *
+ * This class is a little bit different from LowResScrollHandler class - it has a filtering
+ * for events which are coming with wrong sign ( this happens to rsb devices,
+ * especially at the end of the scroll)
+ *
+ * This scroll handler supports fling. It can be set with [RotaryFlingBehavior].
+ */
+internal class HighResRotaryScrollHandler(
+        private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?,
+        private val scrollBehaviorFactory: () -> RotaryScrollBehavior,
+        private val hapticsThreshold: Long = 50,
+) : RotaryScrollHandler {
+
+    // This constant is specific for high-res devices. Because that input values
+    // can sometimes come with different sign, we have to filter them in this threshold
+    private val gestureThresholdTime = 200L
+    private var scrollJob: Job = CompletableDeferred<Unit>()
+    private var flingJob: Job = CompletableDeferred<Unit>()
+
+    private var previousScrollEventTime = 0L
+    private var rotaryScrollDistance = 0f
+
+    private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory()
+    private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory()
+
+    override suspend fun handleScrollEvent(
+            coroutineScope: CoroutineScope,
+            event: TimestampedDelta,
+            rotaryHaptics: RotaryHapticHandler,
+    ) {
+        val time = event.timestamp
+        val isOppositeScrollValue = isOppositeValueAfterScroll(event.delta)
+
+        if (isNewScrollEvent(time)) {
+            debugLog { "New scroll event" }
+            resetTracking(time)
+            rotaryScrollDistance = event.delta
+        } else {
+            // Due to the physics of Rotary side button, some events might come
+            // with an opposite axis value - either at the start or at the end of the motion.
+            // We don't want to use these values for fling calculations.
+            if (!isOppositeScrollValue) {
+                rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta)
+            } else {
+                debugLog { "Opposite value after scroll :${event.delta}" }
+            }
+            rotaryScrollDistance += event.delta
+        }
+
+        scrollJob.cancel()
+
+        rotaryHaptics.handleScrollHaptic(event.delta)
+        debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
+
+        previousScrollEventTime = time
+        scrollJob = coroutineScope.async {
+            scrollBehavior.handleEvent(rotaryScrollDistance)
+        }
+
+        if (rotaryFlingBehavior != null) {
+            flingJob.cancel()
+            flingJob = coroutineScope.async {
+                rotaryFlingBehavior?.trackFling(beforeFling = {
+                    debugLog { "Calling before fling section" }
+                    scrollJob.cancel()
+                    scrollBehavior = scrollBehaviorFactory()
+                })
+            }
+        }
+    }
+
+    private fun isOppositeValueAfterScroll(delta: Float): Boolean =
+            sign(rotaryScrollDistance) * sign(delta) == -1f &&
+                    (abs(delta) < abs(rotaryScrollDistance))
+
+    private fun isNewScrollEvent(timestamp: Long): Boolean {
+        val timeDelta = timestamp - previousScrollEventTime
+        return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
+    }
+
+    private fun resetTracking(timestamp: Long) {
+        scrollBehavior = scrollBehaviorFactory()
+        rotaryFlingBehavior = rotaryFlingBehaviorFactory()
+        rotaryFlingBehavior?.startFlingTracking(timestamp)
+    }
+}
+
+/**
+ * A scroll handler for Bezel(low-res) without snapping.
+ * This scroll handler supports fling. It can be set with RotaryFlingBehavior.
+ */
+internal class LowResRotaryScrollHandler(
+        private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?,
+        private val scrollBehaviorFactory: () -> RotaryScrollBehavior,
+) : RotaryScrollHandler {
+
+    private val gestureThresholdTime = 200L
+    private var previousScrollEventTime = 0L
+    private var rotaryScrollDistance = 0f
+
+    private var scrollJob: Job = CompletableDeferred<Unit>()
+    private var flingJob: Job = CompletableDeferred<Unit>()
+
+    private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory()
+    private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory()
+
+    override suspend fun handleScrollEvent(
+            coroutineScope: CoroutineScope,
+            event: TimestampedDelta,
+            rotaryHaptics: RotaryHapticHandler,
+    ) {
+        val time = event.timestamp
+
+        if (isNewScrollEvent(time)) {
+            resetTracking(time)
+            rotaryScrollDistance = event.delta
+        } else {
+            rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta)
+            rotaryScrollDistance += event.delta
+        }
+
+        scrollJob.cancel()
+        flingJob.cancel()
+
+        rotaryHaptics.handleScrollHaptic(event.delta)
+        debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
+
+        previousScrollEventTime = time
+        scrollJob = coroutineScope.async {
+            scrollBehavior.handleEvent(rotaryScrollDistance)
+        }
+
+        flingJob = coroutineScope.async {
+            rotaryFlingBehavior?.trackFling(
+                    beforeFling = {
+                        debugLog { "Calling before fling section" }
+                        scrollJob.cancel()
+                        scrollBehavior = scrollBehaviorFactory()
+                    },
+            )
+        }
+    }
+
+    private fun isNewScrollEvent(timestamp: Long): Boolean {
+        val timeDelta = timestamp - previousScrollEventTime
+        return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
+    }
+
+    private fun resetTracking(timestamp: Long) {
+        scrollBehavior = scrollBehaviorFactory()
+        debugLog { "Velocity tracker reset" }
+        rotaryFlingBehavior = rotaryFlingBehaviorFactory()
+        rotaryFlingBehavior?.startFlingTracking(timestamp)
+    }
+}
+
+/**
+ * A scroll handler for RSB(high-res) with snapping and without fling
+ * Snapping happens after a threshold is reached ( set in [RotarySnapBehavior])
+ *
+ * This scroll handler doesn't support fling.
+ */
+internal class HighResSnapHandler(
+        private val resistanceFactor: Float,
+        private val thresholdBehaviorFactory: () -> ThresholdBehavior,
+        private val snapBehaviourFactory: () -> RotarySnapBehavior,
+        private val scrollBehaviourFactory: () -> RotaryScrollBehavior,
+) : RotaryScrollHandler {
+    private val gestureThresholdTime = 200L
+    private val snapDelay = 100L
+    private val maxSnapsPerEvent = 2
+
+    private var scrollJob: Job = CompletableDeferred<Unit>()
+    private var snapJob: Job = CompletableDeferred<Unit>()
+
+    private var previousScrollEventTime = 0L
+    private var snapAccumulator = 0f
+    private var rotaryScrollDistance = 0f
+    private var scrollInProgress = false
+
+    private var snapBehaviour = snapBehaviourFactory()
+    private var scrollBehaviour = scrollBehaviourFactory()
+    private var thresholdBehavior = thresholdBehaviorFactory()
+
+    private val scrollEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.5f, 1.0f)
+
+    override suspend fun handleScrollEvent(
+            coroutineScope: CoroutineScope,
+            event: TimestampedDelta,
+            rotaryHaptics: RotaryHapticHandler,
+    ) {
+        val time = event.timestamp
+
+        if (isNewScrollEvent(time)) {
+            debugLog { "New scroll event" }
+            resetTracking()
+            snapJob.cancel()
+            snapBehaviour = snapBehaviourFactory()
+            scrollBehaviour = scrollBehaviourFactory()
+            thresholdBehavior = thresholdBehaviorFactory()
+            thresholdBehavior.startThresholdTracking(time)
+            snapAccumulator = 0f
+            rotaryScrollDistance = 0f
+        }
+
+        if (!isOppositeValueAfterScroll(event.delta)) {
+            thresholdBehavior.observeEvent(event.timestamp, event.delta)
+        } else {
+            debugLog { "Opposite value after scroll :${event.delta}" }
+        }
+
+        thresholdBehavior.applySmoothing()
+        val snapThreshold = thresholdBehavior.snapThreshold()
+
+        snapAccumulator += event.delta
+        if (!snapJob.isActive) {
+            val resistanceCoeff =
+                    1 - scrollEasing.transform(rotaryScrollDistance.absoluteValue / snapThreshold)
+            rotaryScrollDistance += event.delta * resistanceCoeff
+        }
+
+        debugLog { "Snap accumulator: $snapAccumulator" }
+        debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
+
+        debugLog { "snapThreshold: $snapThreshold" }
+        previousScrollEventTime = time
+
+        if (abs(snapAccumulator) > snapThreshold) {
+            scrollInProgress = false
+            scrollBehaviour = scrollBehaviourFactory()
+            scrollJob.cancel()
+
+            val snapDistance = (snapAccumulator / snapThreshold).toInt()
+                    .coerceIn(-maxSnapsPerEvent..maxSnapsPerEvent)
+            snapAccumulator -= snapThreshold * snapDistance
+            val sequentialSnap = snapJob.isActive
+
+            debugLog {
+                "Snap threshold reached: snapDistance:$snapDistance, " +
+                        "sequentialSnap: $sequentialSnap, " +
+                        "snap accumulator remaining: $snapAccumulator"
+            }
+            if ((!snapBehaviour.topEdgeReached() && snapDistance < 0) ||
+                    (!snapBehaviour.bottomEdgeReached() && snapDistance > 0)
+            ) {
+                rotaryHaptics.handleSnapHaptic(event.delta)
+            }
+
+            snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap)
+            if (!snapJob.isActive) {
+                snapJob.cancel()
+                snapJob = coroutineScope.async {
+                    debugLog { "Snap started" }
+                    try {
+                        snapBehaviour.snapToTargetItem()
+                    } finally {
+                        debugLog { "Snap called finally" }
+                    }
+                }
+            }
+            rotaryScrollDistance = 0f
+        } else {
+            if (!snapJob.isActive) {
+                scrollJob.cancel()
+                debugLog { "Scrolling for $rotaryScrollDistance/$resistanceFactor px" }
+                scrollJob = coroutineScope.async {
+                    scrollBehaviour.handleEvent(rotaryScrollDistance / resistanceFactor)
+                }
+                delay(snapDelay)
+                scrollInProgress = false
+                scrollBehaviour = scrollBehaviourFactory()
+                rotaryScrollDistance = 0f
+                snapAccumulator = 0f
+                snapBehaviour.prepareSnapForItems(0, false)
+
+                snapJob.cancel()
+                snapJob = coroutineScope.async {
+                    snapBehaviour.snapToClosestItem()
+                }
+            }
+        }
+    }
+
+    private fun isOppositeValueAfterScroll(delta: Float): Boolean =
+            sign(rotaryScrollDistance) * sign(delta) == -1f &&
+                    (abs(delta) < abs(rotaryScrollDistance))
+
+    private fun isNewScrollEvent(timestamp: Long): Boolean {
+        val timeDelta = timestamp - previousScrollEventTime
+        return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
+    }
+
+    private fun resetTracking() {
+        scrollInProgress = true
+    }
+}
+
+/**
+ * A scroll handler for RSB(high-res) with snapping and without fling
+ * Snapping happens after a threshold is reached ( set in [RotarySnapBehavior])
+ *
+ * This scroll handler doesn't support fling.
+ */
+internal class LowResSnapHandler(
+        private val snapBehaviourFactory: () -> RotarySnapBehavior,
+) : RotaryScrollHandler {
+    private val gestureThresholdTime = 200L
+
+    private var snapJob: Job = CompletableDeferred<Unit>()
+
+    private var previousScrollEventTime = 0L
+    private var snapAccumulator = 0f
+    private var scrollInProgress = false
+
+    private var snapBehaviour = snapBehaviourFactory()
+
+    override suspend fun handleScrollEvent(
+            coroutineScope: CoroutineScope,
+            event: TimestampedDelta,
+            rotaryHaptics: RotaryHapticHandler,
+    ) {
+        val time = event.timestamp
+
+        if (isNewScrollEvent(time)) {
+            debugLog { "New scroll event" }
+            resetTracking()
+            snapJob.cancel()
+            snapBehaviour = snapBehaviourFactory()
+            snapAccumulator = 0f
+        }
+
+        snapAccumulator += event.delta
+
+        debugLog { "Snap accumulator: $snapAccumulator" }
+
+        previousScrollEventTime = time
+
+        if (abs(snapAccumulator) > 1f) {
+            scrollInProgress = false
+
+            val snapDistance = sign(snapAccumulator).toInt()
+            rotaryHaptics.handleSnapHaptic(event.delta)
+            val sequentialSnap = snapJob.isActive
+            debugLog {
+                "Snap threshold reached: snapDistance:$snapDistance, " +
+                        "sequentialSnap: $sequentialSnap, " +
+                        "snap accumulator: $snapAccumulator"
+            }
+
+            snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap)
+            if (!snapJob.isActive) {
+                snapJob.cancel()
+                snapJob = coroutineScope.async {
+                    debugLog { "Snap started" }
+                    try {
+                        snapBehaviour.snapToTargetItem()
+                    } finally {
+                        debugLog { "Snap called finally" }
+                    }
+                }
+            }
+            snapAccumulator = 0f
+        }
+    }
+
+    private fun isNewScrollEvent(timestamp: Long): Boolean {
+        val timeDelta = timestamp - previousScrollEventTime
+        return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
+    }
+
+    private fun resetTracking() {
+        scrollInProgress = true
+    }
+}
+
+internal class ThresholdBehavior(
+        private val rotaryScrollAdapter: RotaryScrollAdapter,
+        private val thresholdDivider: Float,
+        private val minVelocity: Float = 300f,
+        private val maxVelocity: Float = 3000f,
+        private val smoothingConstant: Float = 0.4f,
+) {
+    private val thresholdDividerEasing: Easing = CubicBezierEasing(0.5f, 0.0f, 0.5f, 1.0f)
+
+    private val rotaryVelocityTracker = RotaryVelocityTracker()
+
+    private var smoothedVelocity = 0f
+    fun startThresholdTracking(time: Long) {
+        rotaryVelocityTracker.start(time)
+        smoothedVelocity = 0f
+    }
+
+    fun observeEvent(timestamp: Long, delta: Float) {
+        rotaryVelocityTracker.move(timestamp, delta)
+    }
+
+    fun applySmoothing() {
+        if (rotaryVelocityTracker.velocity != 0.0f) {
+            // smooth the velocity
+            smoothedVelocity = exponentialSmoothing(
+                    currentVelocity = rotaryVelocityTracker.velocity.absoluteValue,
+                    prevVelocity = smoothedVelocity,
+                    smoothingConstant = smoothingConstant,
+            )
+        }
+        debugLog { "rotaryVelocityTracker velocity: ${rotaryVelocityTracker.velocity}" }
+        debugLog { "SmoothedVelocity: $smoothedVelocity" }
+    }
+
+    fun snapThreshold(): Float {
+        val thresholdDividerFraction =
+                thresholdDividerEasing.transform(
+                        inverseLerp(
+                                minVelocity,
+                                maxVelocity,
+                                smoothedVelocity,
+                        ),
+                )
+        return rotaryScrollAdapter.averageItemSize() / lerp(
+                1f,
+                thresholdDivider,
+                thresholdDividerFraction,
+        )
+    }
+
+    private fun exponentialSmoothing(
+            currentVelocity: Float,
+            prevVelocity: Float,
+            smoothingConstant: Float,
+    ): Float =
+            smoothingConstant * currentVelocity + (1 - smoothingConstant) * prevVelocity
+}
+
+@Composable
+private fun rememberTimestampChannel() = remember {
+    Channel<TimestampedDelta>(capacity = Channel.CONFLATED)
+}
+
+private fun inverseLerp(start: Float, stop: Float, value: Float): Float {
+    return ((value - start) / (stop - start)).coerceIn(0f, 1f)
+}
diff --git a/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/RotaryVelocityTracker.kt b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/RotaryVelocityTracker.kt
new file mode 100644
index 0000000..6e627c6
--- /dev/null
+++ b/packages/CredentialManager/horologist/src/com/google/android/horologist/compose/rotaryinput/RotaryVelocityTracker.kt
@@ -0,0 +1,54 @@
+/*
+ * 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
+ *
+ *      https://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.google.android.horologist.compose.rotaryinput
+
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+
+/**
+ * A wrapper around VelocityTracker1D to provide support for rotary input.
+ */
+public class RotaryVelocityTracker {
+    private var velocityTracker: VelocityTracker1D = VelocityTracker1D(true)
+
+    /**
+     * Retrieve the last computed velocity.
+     */
+    public val velocity: Float
+        get() = velocityTracker.calculateVelocity()
+
+    /**
+     * Start tracking motion.
+     */
+    public fun start(currentTime: Long) {
+        velocityTracker.resetTracking()
+        velocityTracker.addDataPoint(currentTime, 0f)
+    }
+
+    /**
+     * Continue tracking motion as the input rotates.
+     */
+    public fun move(currentTime: Long, delta: Float) {
+        velocityTracker.addDataPoint(currentTime, delta)
+    }
+
+    /**
+     * Stop tracking motion.
+     */
+    public fun end() {
+        velocityTracker.resetTracking()
+    }
+}
diff --git a/packages/CredentialManager/res/xml/autofill_service_configuration.xml b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
new file mode 100644
index 0000000..25cc094
--- /dev/null
+++ b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
@@ -0,0 +1,10 @@
+<?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
+-->
+<autofill-service-configuration
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsInlineSuggestions="true"/>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
new file mode 100644
index 0000000..943c2b4
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.credentialmanager.autofill
+
+import android.os.CancellationSignal
+import android.service.autofill.AutofillService
+import android.service.autofill.FillCallback
+import android.service.autofill.FillRequest
+import android.service.autofill.SaveRequest
+import android.service.autofill.SaveCallback
+
+class CredentialAutofillService : AutofillService() {
+    override fun onFillRequest(
+            request: FillRequest,
+            cancellationSignal: CancellationSignal,
+            callback: FillCallback
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
+        TODO("Not yet implemented")
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/Android.bp b/packages/CredentialManager/wear/Android.bp
new file mode 100644
index 0000000..639e8d1
--- /dev/null
+++ b/packages/CredentialManager/wear/Android.bp
@@ -0,0 +1,53 @@
+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: "ClockworkCredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+    manifest: "AndroidManifest.xml",
+    srcs: ["src/**/*.kt"],
+    resource_dirs: ["res"],
+
+    dex_preopt: {
+        profile_guided: true,
+        profile: "profile.txt.prof",
+    },
+
+    static_libs: [
+        "Horologist",
+        "PlatformComposeCore",
+        "androidx.activity_activity-compose",
+        "androidx.appcompat_appcompat",
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.foundation_foundation-layout",
+        "androidx.compose.material_material-icons-core",
+        "androidx.compose.material_material-icons-extended",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.ui_ui",
+        "androidx.core_core-ktx",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "androidx.wear.compose_compose-foundation",
+        "androidx.wear.compose_compose-material",
+        "androidx.wear.compose_compose-navigation",
+        "kotlinx-coroutines-core",
+    ],
+
+    platform_apis: true,
+    privileged: true,
+
+    kotlincflags: ["-Xjvm-default=all"],
+
+    optimize: {
+        proguard_compatibility: false,
+    },
+}
diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml
index 001a56d..90248734 100644
--- a/packages/CredentialManager/wear/AndroidManifest.xml
+++ b/packages/CredentialManager/wear/AndroidManifest.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
 /*
- * Copyright (c) 2017 Google Inc.
+ * Copyright (c) 2023 Google Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.credentialmanager">
 
+    <uses-feature android:name="android.hardware.type.watch" />
+
     <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
     <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
@@ -28,17 +30,15 @@
       android:dataExtractionRules="@xml/data_extraction_rules"
       android:fullBackupContent="@xml/backup_rules"
       android:label="@string/app_name"
-      android:supportsRtl="true"
-      android:theme="@style/Theme.CredentialSelector">
+      android:supportsRtl="true">
 
         <activity
-            android:name=".CredentialSelectorActivity"
+            android:name=".ui.CredentialSelectorActivity"
             android:exported="true"
             android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
             android:launchMode="singleTop"
             android:label="@string/app_name"
-            android:excludeFromRecents="true"
-            android:theme="@style/Theme.CredentialSelector">
+            android:excludeFromRecents="true">
         </activity>
   </application>
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt
similarity index 68%
rename from packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
rename to packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt
index f7b2499..77fffaa 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/CredentialSelectorActivity.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2023 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,19 +14,29 @@
  * limitations under the License.
  */
 
+package com.android.credentialmanager.ui
+
 import android.os.Bundle
-import androidx.activity.compose.setContent
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.navigation.NavHostController
 import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.Text
+import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
 
 class CredentialSelectorActivity : ComponentActivity() {
+
+    lateinit var navController: NavHostController
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        setTheme(android.R.style.Theme_DeviceDefault)
+
         setContent {
+            navController = rememberSwipeDismissableNavController()
+
             MaterialTheme {
-                Text("Credential Manager entry point")
+                WearApp(navController = navController)
             }
         }
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt
new file mode 100644
index 0000000..ee6ea5e
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/Screen.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.ui
+
+sealed class Screen(
+    val route: String,
+) {
+    object Main : Screen("main")
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
new file mode 100644
index 0000000..5ec0c8c
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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:OptIn(ExperimentalHorologistApi::class)
+
+package com.android.credentialmanager.ui
+
+import androidx.compose.runtime.Composable
+import androidx.navigation.NavHostController
+import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
+import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
+import com.android.credentialmanager.ui.screens.MainScreen
+import com.google.android.horologist.annotations.ExperimentalHorologistApi
+import com.google.android.horologist.compose.navscaffold.WearNavScaffold
+import com.google.android.horologist.compose.navscaffold.composable
+
+@Composable
+fun WearApp(
+    navController: NavHostController
+) {
+    val swipeToDismissBoxState = rememberSwipeToDismissBoxState()
+    val navHostState =
+        rememberSwipeDismissableNavHostState(swipeToDismissBoxState = swipeToDismissBoxState)
+
+    WearNavScaffold(
+        startDestination = Screen.Main.route,
+        navController = navController,
+        state = navHostState,
+    ) {
+        composable(Screen.Main.route) {
+            MainScreen()
+        }
+    }
+}
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt
new file mode 100644
index 0000000..662d710
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/MainScreen.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.0N
+ *
+ * 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.credentialmanager.ui.screens
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.wear.compose.material.Text
+
+@Composable
+fun MainScreen(modifier: Modifier = Modifier) {
+    Box(modifier = modifier, contentAlignment = Alignment.Center) {
+        Text("This is a placeholder for the main screen.")
+    }
+}
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 5bd422b..a16f9f5 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -70,15 +70,18 @@
             </intent-filter>
         </activity>
 
+        <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem
+            is resolved for new implementation -->
         <activity android:name=".InstallStaging"
-                android:exported="false" />
+                  android:theme="@style/Theme.AlertDialogActivity.NoDim"
+                  android:exported="false" />
 
         <activity android:name=".DeleteStagedFileOnResult"
             android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
             android:exported="false" />
 
         <activity android:name=".PackageInstallerActivity"
-                android:exported="false" />
+                  android:exported="false" />
 
         <activity android:name=".InstallInstalling"
                 android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 9a06229..aa1fa16 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,4 +32,9 @@
         <item name="android:windowNoTitle">true</item>
     </style>
 
+    <style name="Theme.AlertDialogActivity.NoDim">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:backgroundDimAmount">0</item>
+    </style>
+
 </resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 80e8761..d97fb54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -306,6 +306,7 @@
     }
 
     private void initiateInstall() {
+        bindUi();
         String pkgName = mPkgInfo.packageName;
         // Check if there is already a package on the device with this name
         // but it has been renamed to something else.
@@ -445,7 +446,6 @@
         if (mAppSnippet != null) {
             // load placeholder layout with OK button disabled until we override this layout in
             // startInstallConfirm
-            bindUi();
             checkIfAllowedAndInitiateInstall();
         }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 471f3b9..01596d2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -32,6 +32,7 @@
 import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
 import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
+import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
@@ -44,6 +45,7 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
 import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.slice.SpaSliceBroadcastReceiver
@@ -92,6 +94,8 @@
                 SettingsOutlinedTextFieldPageProvider,
                 SettingsExposedDropdownMenuBoxPageProvider,
                 SettingsExposedDropdownMenuCheckBoxProvider,
+                SettingsTextFieldPasswordPageProvider,
+                SearchScaffoldPageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index b74af21..4875ea9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -39,6 +39,8 @@
                 .build(),
             SettingsExposedDropdownMenuCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
                 .build(),
+            SettingsTextFieldPasswordPageProvider.buildInjectEntry().setLink(fromPage = owner)
+                .build(),
         )
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt
new file mode 100644
index 0000000..fc51466e
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.editor
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TITLE = "Sample SettingsTextFieldPassword"
+
+object SettingsTextFieldPasswordPageProvider : SettingsPageProvider {
+    override val name = "SettingsTextFieldPassword"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        var value by remember { mutableStateOf("value") }
+        RegularScaffold(title = TITLE) {
+            SettingsTextFieldPassword(
+                value = value,
+                label = "label",
+                onTextChange = { value = it })
+        }
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsTextFieldPasswordPagePreview() {
+    SettingsTheme {
+        SettingsTextFieldPasswordPageProvider.Page(null)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 6cac220..b339b44 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -41,6 +41,7 @@
 import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider
 import com.android.settingslib.spa.gallery.page.SliderPageProvider
 import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
+import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
 import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -55,6 +56,7 @@
             PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
+            SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
new file mode 100644
index 0000000..a1ab35b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.scaffold
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.SearchScaffold
+import com.android.settingslib.spa.widget.ui.PlaceholderTitle
+
+private const val TITLE = "Sample SearchScaffold"
+
+object SearchScaffoldPageProvider : SettingsPageProvider {
+    override val name = "SearchScaffold"
+
+    private val owner = createSettingsPage()
+
+    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
+        .setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            })
+        }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        Page()
+    }
+}
+
+@Composable
+private fun Page() {
+    SearchScaffold(title = TITLE) { bottomPadding, searchQuery ->
+        PlaceholderTitle("Search query: ${searchQuery.value}")
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 08e3a27..9f8c868 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,6 +22,7 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
@@ -126,18 +127,22 @@
     allProvider: Collection<SettingsPageProvider>,
     content: @Composable (SettingsPage) -> Unit,
 ) {
-    NavHost(
-        navController = navController,
-        startDestination = NullPageProvider.name,
-    ) {
-        composable(NullPageProvider.name) {}
-        for (spp in allProvider) {
-            animatedComposable(
-                route = spp.name + spp.parameter.navRoute(),
-                arguments = spp.parameter,
-            ) { navBackStackEntry ->
-                val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
-                content(page)
+    // TODO(b/298520326): Remove Box after the issue is fixed.
+    // Wrap the top level node into a Box to workaround an issue of Compose 1.6.0-alpha03.
+    Box {
+        NavHost(
+            navController = navController,
+            startDestination = NullPageProvider.name,
+        ) {
+            composable(NullPageProvider.name) {}
+            for (spp in allProvider) {
+                animatedComposable(
+                    route = spp.name + spp.parameter.navRoute(),
+                    arguments = spp.parameter,
+                ) { navBackStackEntry ->
+                    val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
+                    content(page)
+                }
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 682b4ea..3260094 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -131,7 +131,8 @@
     val options = listOf("item1", "item2", "item3")
     val selectedOptionsState = remember { mutableStateListOf("item1", "item2") }
     SettingsTheme {
-        SettingsExposedDropdownMenuCheckBox(label = "label",
+        SettingsExposedDropdownMenuCheckBox(
+            label = "label",
             options = options,
             selectedOptionsState = selectedOptionsState,
             enabled = true,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
new file mode 100644
index 0000000..d0a6188
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Visibility
+import androidx.compose.material.icons.outlined.VisibilityOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun SettingsTextFieldPassword(
+    value: String,
+    label: String,
+    onTextChange: (String) -> Unit,
+) {
+    var visibility by remember { mutableStateOf(false) }
+    OutlinedTextField(
+        modifier = Modifier
+            .padding(SettingsDimension.itemPadding)
+            .fillMaxWidth(),
+        value = value,
+        onValueChange = onTextChange,
+        label = { Text(text = label) },
+        keyboardOptions = KeyboardOptions(
+            keyboardType = KeyboardType.Password,
+            imeAction = ImeAction.Send
+        ),
+        trailingIcon = {
+            Icon(
+                imageVector = if (visibility) Icons.Outlined.VisibilityOff
+                else Icons.Outlined.Visibility,
+                contentDescription = "Visibility Icon",
+                modifier = Modifier
+                    .testTag("Visibility Icon")
+                    .size(SettingsDimension.itemIconSize)
+                    .toggleable(visibility) {
+                        visibility = !visibility
+                    },
+            )
+        },
+        visualTransformation = if (visibility) VisualTransformation.None
+        else PasswordVisualTransformation()
+    )
+}
+
+@Preview
+@Composable
+private fun SettingsTextFieldPasswordPreview() {
+    var value by remember { mutableStateOf("value") }
+    SettingsTheme {
+        SettingsTextFieldPassword(
+            value = value,
+            label = "label",
+            onTextChange = { value = it },
+        )
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index b77368a..3a0e51b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -32,8 +32,8 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.material3.ExperimentalMaterial3Api
@@ -49,10 +49,8 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clipToBounds
@@ -73,7 +71,6 @@
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.compose.horizontalValues
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import kotlin.math.abs
@@ -129,16 +126,10 @@
 private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
     Text(
         text = title,
-        modifier = Modifier
-            .padding(
-                WindowInsets.navigationBars
-                    .asPaddingValues()
-                    .horizontalValues()
-            )
-            .padding(
-                start = SettingsDimension.itemPaddingAround,
-                end = SettingsDimension.itemPaddingEnd,
-            ),
+        modifier = Modifier.padding(
+            start = SettingsDimension.itemPaddingAround,
+            end = SettingsDimension.itemPaddingEnd,
+        ),
         overflow = TextOverflow.Ellipsis,
         maxLines = maxLines,
     )
@@ -157,6 +148,15 @@
  * Represents the colors used by a top app bar in different states.
  * This implementation animates the container color according to the top app bar scroll state. It
  * does not animate the leading, headline, or trailing colors.
+ *
+ * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a
+ * factory method using the default material3 spec
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param scrolledContainerColor the container color when content is scrolled behind it
+ * @param navigationIconContentColor the content color used for the navigation icon
+ * @param titleContentColor the content color used for the title
+ * @param actionIconContentColor the content color used for actions
  */
 @Stable
 private class TopAppBarColors(
@@ -248,7 +248,6 @@
             titleTextStyle = titleTextStyle,
             titleAlpha = 1f,
             titleVerticalArrangement = Arrangement.Center,
-            titleHorizontalArrangement = Arrangement.Start,
             titleBottomPadding = 0,
             hideTitleSemantics = false,
             navigationIcon = navigationIcon,
@@ -312,7 +311,7 @@
     // This will potentially animate or interpolate a transition between the container color and the
     // container's scrolled color according to the app bar's scroll state.
     val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
-    val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+    val appBarContainerColor = colors.containerColor(colorTransitionFraction)
 
     // Wrap the given actions in a Row.
     val actionsRow = @Composable {
@@ -364,14 +363,17 @@
                 titleTextStyle = smallTitleTextStyle,
                 titleAlpha = topTitleAlpha,
                 titleVerticalArrangement = Arrangement.Center,
-                titleHorizontalArrangement = Arrangement.Start,
                 titleBottomPadding = 0,
                 hideTitleSemantics = hideTopRowSemantics,
                 navigationIcon = navigationIcon,
                 actions = actionsRow,
             )
             TopAppBarLayout(
-                modifier = Modifier.clipToBounds(),
+                modifier = Modifier
+                    // only apply the horizontal sides of the window insets padding, since the top
+                    // padding will always be applied by the layout above
+                    .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
+                    .clipToBounds(),
                 heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                     (scrollBehavior?.state?.heightOffset ?: 0f),
                 navigationIconContentColor = colors.navigationIconContentColor,
@@ -392,7 +394,6 @@
                 titleTextStyle = titleTextStyle,
                 titleAlpha = bottomTitleAlpha,
                 titleVerticalArrangement = Arrangement.Bottom,
-                titleHorizontalArrangement = Arrangement.Start,
                 titleBottomPadding = titleBottomPaddingPx,
                 hideTitleSemantics = hideBottomRowSemantics,
                 navigationIcon = {},
@@ -419,7 +420,6 @@
  * @param modifier a [Modifier]
  * @param titleAlpha the title's alpha
  * @param titleVerticalArrangement the title's vertical arrangement
- * @param titleHorizontalArrangement the title's horizontal arrangement
  * @param titleBottomPadding the title's bottom padding
  * @param hideTitleSemantics hides the title node from the semantic tree. Apply this
  * boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
@@ -440,7 +440,6 @@
     titleTextStyle: TextStyle,
     titleAlpha: Float,
     titleVerticalArrangement: Arrangement.Vertical,
-    titleHorizontalArrangement: Arrangement.Horizontal,
     titleBottomPadding: Int,
     hideTitleSemantics: Boolean,
     navigationIcon: @Composable () -> Unit,
@@ -470,10 +469,10 @@
                     CompositionLocalProvider(
                         LocalContentColor provides titleContentColor,
                         LocalDensity provides with(LocalDensity.current) {
-                          Density(
-                              density = density,
-                              fontScale = if (titleScaleDisabled) 1f else fontScale,
-                          )
+                            Density(
+                                density = density,
+                                fontScale = if (titleScaleDisabled) 1f else fontScale,
+                            )
                         },
                         content = title
                     )
@@ -528,15 +527,7 @@
 
             // Title
             titlePlaceable.placeRelative(
-                x = when (titleHorizontalArrangement) {
-                    Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
-                    Arrangement.End ->
-                        constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
-                    // Arrangement.Start.
-                    // A TopAppBarTitleInset will make sure the title is offset in case the
-                    // navigation icon is missing.
-                    else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
-                },
+                x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width),
                 y = when (titleVerticalArrangement) {
                     Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
                     // Apply bottom padding from the title's baseline only when the Arrangement is
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt
new file mode 100644
index 0000000..6f2d1f9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertTextContains
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextReplacement
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsTextFieldPasswordTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+    private val label = "label"
+    private val value = "value"
+    private val valueChanged = "Value Changed"
+    private val visibilityIconTag = "Visibility Icon"
+
+    @Test
+    fun textFieldPassword_displayed() {
+        composeTestRule.setContent {
+            SettingsTextFieldPassword(
+                value = value,
+                label = label,
+                onTextChange = {})
+        }
+        composeTestRule.onNodeWithText(label, substring = true)
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun textFieldPassword_invisible() {
+        composeTestRule.setContent {
+            var value by remember { mutableStateOf(value) }
+            SettingsTextFieldPassword(
+                value = value,
+                label = label,
+                onTextChange = { value = it })
+        }
+        composeTestRule.onNodeWithText(value, substring = true)
+            .assertDoesNotExist()
+    }
+
+    @Test
+    fun textFieldPassword_visible_inputValue() {
+        composeTestRule.setContent {
+            var value by remember { mutableStateOf(value) }
+            SettingsTextFieldPassword(
+                value = value,
+                label = label,
+                onTextChange = { value = it })
+        }
+        composeTestRule.onNodeWithTag(visibilityIconTag)
+            .performClick()
+        composeTestRule.onNodeWithText(label, substring = true)
+            .performTextReplacement(valueChanged)
+        composeTestRule.onNodeWithText(label, substring = true)
+            .assertTextContains(valueChanged)
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
index a6a5ed22..3dac7db 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -22,7 +22,6 @@
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
 import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.CenterAlignedTopAppBar
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
@@ -307,8 +306,8 @@
     }
 
     /**
-     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
-     * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar]
+     * or a larger app bar that is scrolled up and collapsed into a small
      * configuration and there is no navigation icon.
      */
     private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
@@ -335,8 +334,7 @@
     }
 
     /**
-     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
-     * [CenterAlignedTopAppBar].
+     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar].
      */
     private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
         val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 91b852a..70126ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -198,19 +198,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
     boolean isA2dpPlaying() {
         if (mService == null) return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index c7a5bd8..38cd04e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -140,19 +140,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     boolean isAudioPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
index c3f845c..45cafc6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
@@ -178,19 +178,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null || device == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 6b7f733..660090d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -258,19 +258,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null || device == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 7e5c124..b79f147 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -165,19 +165,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 5fbb4c3..14fab16 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -231,19 +231,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null || device == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 66225a2..b0e0049 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -150,19 +150,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 8a2c4f8..5468efb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -123,13 +123,13 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         // if set preferred to false, then disconnect to the current device
         if (!enabled) {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 3c24b4a..5b91ac9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -126,19 +126,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index e781f13..a93524f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -24,24 +24,18 @@
 import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.os.Build;
-import android.os.ParcelUuid;
 import android.util.Log;
 
 import androidx.annotation.RequiresApi;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 public class LeAudioProfile implements LocalBluetoothProfile {
@@ -233,19 +227,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null || device == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 4881a86..0a803bc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -137,19 +137,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 75c1926..db1ba5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -138,19 +138,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 767df35..dec862b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -105,7 +105,7 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
@@ -117,12 +117,12 @@
                     mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN);
                 }
             }
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 0d11293..65b89ba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -151,19 +151,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 9e2e4a1..784b821 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -108,16 +108,16 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
 
         if (!enabled) {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 104f1d7..0f8892f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -136,19 +136,19 @@
 
     @Override
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
-        boolean isEnabled = false;
+        boolean isSuccessful = false;
         if (mService == null) {
             return false;
         }
         if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
 
-        return isEnabled;
+        return isSuccessful;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
deleted file mode 100644
index 54d5c3d..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
+++ /dev/null
@@ -1,107 +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.settingslib.net;
-
-import android.app.usage.NetworkStats;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.util.Log;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-/**
- * Loader for retrieving the network stats summary for all UIDs.
- */
-public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> {
-
-    private static final String TAG = "NetworkDetailLoader";
-    private final NetworkStatsManager mNetworkStatsManager;
-    private final long mStart;
-    private final long mEnd;
-    private final NetworkTemplate mNetworkTemplate;
-
-    private NetworkStatsSummaryLoader(Builder builder) {
-        super(builder.mContext);
-        mStart = builder.mStart;
-        mEnd = builder.mEnd;
-        mNetworkTemplate = builder.mNetworkTemplate;
-        mNetworkStatsManager = (NetworkStatsManager)
-                builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
-    }
-
-    @Override
-    protected void onStartLoading() {
-        super.onStartLoading();
-        forceLoad();
-    }
-
-    @Override
-    public NetworkStats loadInBackground() {
-        try {
-            return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Exception querying network detail.", e);
-            return null;
-        }
-    }
-
-    @Override
-    protected void onStopLoading() {
-        super.onStopLoading();
-        cancelLoad();
-    }
-
-    @Override
-    protected void onReset() {
-        super.onReset();
-        cancelLoad();
-    }
-
-    public static class Builder {
-        private final Context mContext;
-        private long mStart;
-        private long mEnd;
-        private NetworkTemplate mNetworkTemplate;
-
-        public Builder(Context context) {
-            mContext = context;
-        }
-
-        public Builder setStartTime(long start) {
-            mStart = start;
-            return this;
-        }
-
-        public Builder setEndTime(long end) {
-            mEnd = end;
-            return this;
-        }
-
-        /**
-         * Set {@link NetworkTemplate} for builder
-         */
-        public Builder setNetworkTemplate(NetworkTemplate template) {
-            mNetworkTemplate = template;
-            return this;
-        }
-
-        public NetworkStatsSummaryLoader build() {
-            return new NetworkStatsSummaryLoader(this);
-        }
-    }
-}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index ffe28a6..15620b7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -23,6 +23,7 @@
         >
 
         <!-- Standard permissions granted to the shell. -->
+    <uses-permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER" />
     <uses-permission android:name="android.permission.MANAGE_HEALTH_PERMISSIONS" />
     <uses-permission android:name="android.permission.MANAGE_HEALTH_DATA" />
     <uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTE" />
@@ -852,6 +853,9 @@
     <!-- Permission required for accessing all content provider mime types -->
     <uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
 
+    <!-- Permission required for CTS-in-sandbox tests -->
+    <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" />
+
     <!-- Permission required for CTS test - CtsWallpaperTestCases -->
     <uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
 
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 78da5a6..8f329b3 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -4,7 +4,6 @@
 
 dsandler@android.com
 
-aaliomer@google.com
 aaronjli@google.com
 achalke@google.com
 acul@google.com
@@ -36,6 +35,7 @@
 gwasserman@google.com
 hwwang@google.com
 hyunyoungs@google.com
+ikateryna@google.com
 jaggies@google.com
 jamesoleary@google.com
 jbolinger@google.com
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 03cbc16..0f55f35 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -5,4 +5,11 @@
     namespace: "accessibility"
     description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
     bug: "298467628"
+}
+
+flag {
+    name: "a11y_menu_hide_before_taking_action"
+    namespace: "accessibility"
+    description: "Hides the AccessibilityMenuService UI before taking action instead of after."
+    bug: "292020123"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index 587395d..fd9a9c6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -15,7 +15,7 @@
       android:layout_centerHorizontal="true"
       android:scaleType="fitCenter"/>
 
-<TextView
+  <TextView
       android:id="@+id/shortcutLabel"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
@@ -27,6 +27,6 @@
       android:lines="2"
       android:textSize="@dimen/label_text_size"
       android:textAlignment="center"
-      android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.Button"/>
+      android:textColor="@color/grid_item_text_color"/>
 
 </RelativeLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
index fbf2b07..f961bdb 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
@@ -20,5 +20,6 @@
   <color name="footer_icon_disabled_color">#5F6368</color>
   <color name="colorControlNormal">#202124</color>
   <color name="snackbar_bg_color">@android:color/white</color>
+  <color name="grid_item_text_color">@android:color/white</color>
 
 </resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
index d81d0d5..9722eb0 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
@@ -21,6 +21,7 @@
   <color name="footer_icon_disabled_color">#ddd</color>
   <color name="colorControlNormal">@android:color/white</color>
   <color name="snackbar_bg_color">#313235</color>
+  <color name="grid_item_text_color">@android:color/black</color>
 
   <color name="colorAccessibilityMenuIcon">#3AA757</color>
 </resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 27aade5..008732e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -260,6 +260,27 @@
         // Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle.
         int viewTag = (int) view.getTag();
 
+        // First check if this was a shortcut which should keep a11y menu visible. If so,
+        // perform the shortcut and return without hiding the UI.
+        if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+            adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
+            return;
+        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
+            adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
+            return;
+        } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+            adjustVolume(AudioManager.ADJUST_RAISE);
+            return;
+        } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
+            adjustVolume(AudioManager.ADJUST_LOWER);
+            return;
+        }
+
+        if (Flags.a11yMenuHideBeforeTakingAction()) {
+            // Hide the a11y menu UI before performing the following shortcut actions.
+            mA11yMenuLayout.hideMenu();
+        }
+
         if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) {
             // Always restart the voice command activity, so that the UI is reloaded.
             startActivityIfIntentIsSafe(
@@ -281,21 +302,11 @@
             performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS);
         } else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
             performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
-        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
-            adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
-            return;
-        } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
-            adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
-            return;
-        } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
-            adjustVolume(AudioManager.ADJUST_RAISE);
-            return;
-        } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
-            adjustVolume(AudioManager.ADJUST_LOWER);
-            return;
         }
 
-        mA11yMenuLayout.hideMenu();
+        if (!Flags.a11yMenuHideBeforeTakingAction()) {
+            mA11yMenuLayout.hideMenu();
+        }
     }
 
     /**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 767756e..17c74ba 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -263,9 +263,11 @@
             override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
                 delegate.onLaunchAnimationStart(isExpandingFullyAbove)
                 overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+                cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
             }
 
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+                cujType?.let { InteractionJankMonitor.getInstance().end(it) }
                 delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
                 overlay.value = null
             }
@@ -320,9 +322,8 @@
             }
 
             override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
-                // TODO(b/252723237): Add support for jank monitoring when animating from a
-                // Composable.
-                return null
+                val type = cuj?.cujType ?: return null
+                return InteractionJankMonitor.Configuration.Builder.withView(type, composeViewRoot)
             }
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index e06a69b..5505eaf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -82,7 +82,7 @@
 ) : ComposableScene {
     override val key = SceneKey.Bouncer
 
-    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow(
                 mapOf(
                     UserAction.Back to SceneModel(SceneKey.Lockscreen),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index 463253b..f1da168 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -66,13 +66,13 @@
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
-    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
         viewModel.upDestinationSceneKey
             .map { pageKey -> destinationScenes(up = pageKey) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = destinationScenes(up = null)
+                initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
             )
 
     @Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 7ac3901..1f9c3e6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -58,13 +58,14 @@
 ) : ComposableScene {
     override val key = SceneKey.QuickSettings
 
-    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+    private val _destinationScenes =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
                     UserAction.Swipe(Direction.UP) to SceneModel(SceneKey.Shade),
                 )
             )
             .asStateFlow()
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> = _destinationScenes
 
     @Composable
     override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 40b0b4a..2ee461f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -38,7 +38,7 @@
 class GoneScene @Inject constructor() : ComposableScene {
     override val key = SceneKey.Gone
 
-    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
         MutableStateFlow<Map<UserAction, SceneModel>>(
                 mapOf(
                     UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 6a5a368..47af842 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -79,7 +79,7 @@
     val currentSceneKey = currentSceneModel.key
     val currentScene = checkNotNull(sceneByKey[currentSceneKey])
     val currentDestinations: Map<UserAction, SceneModel> by
-        currentScene.destinationScenes().collectAsState()
+        currentScene.destinationScenes.collectAsState()
     val state = remember { SceneTransitionLayoutState(currentSceneKey.toTransitionSceneKey()) }
     val isRibbonEnabled = remember { SystemProperties.getBoolean("flexi.ribbon", false) }
 
@@ -116,7 +116,7 @@
                         if (sceneKey == currentSceneKey) {
                                 currentDestinations
                             } else {
-                                composableScene.destinationScenes().value
+                                composableScene.destinationScenes.value
                             }
                             .map { (userAction, destinationSceneModel) ->
                                 toTransitionModels(userAction, destinationSceneModel)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index b105637..8832a11 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -87,7 +87,7 @@
 ) : ComposableScene {
     override val key = SceneKey.Shade
 
-    override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
+    override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
         viewModel.upDestinationSceneKey
             .map { sceneKey -> destinationScenes(up = sceneKey) }
             .stateIn(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 92d2bd2..9d62e38 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -163,9 +163,6 @@
         const val TABLE_NAME = "flags"
         val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
 
-        /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */
-        const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui"
-
         /**
          * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
          */
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
new file mode 100644
index 0000000..173d57b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, 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.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+     Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPasswordView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_password_view"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:gravity="bottom">
+
+    <!-- Layout here is visually identical to the previous keyguard_password_view.
+         I.E., 'constraints here effectively the same as the previous linear layout' -->
+    <androidx.constraintlayout.motion.widget.MotionLayout
+        android:id="@+id/password_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:maxWidth="@dimen/keyguard_security_width"
+        android:layout_gravity="center_horizontal"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical"
+        androidprv:layoutDescription="@xml/keyguard_password_scene">
+
+        <!-- Guideline need to align password right of centre,
+        when on small screen landscape layout -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/password_center_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintGuide_percent="0.5" />
+
+        <LinearLayout
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:layoutDirection="ltr"
+            android:orientation="vertical"
+            androidprv:layout_constraintTop_toTopOf="parent">
+
+            <include layout="@layout/keyguard_bouncer_message_area" />
+
+            <com.android.systemui.bouncer.ui.BouncerMessageView
+                android:id="@+id/bouncer_message_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" />
+
+        </LinearLayout>
+
+        <FrameLayout
+            android:id="@+id/passwordEntry_container"
+            android:layout_width="280dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:theme="?attr/passwordStyle"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintHorizontal_bias="0.5"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container"
+            androidprv:layout_constraintVertical_bias="0.7777">
+
+            <EditText
+                android:id="@+id/passwordEntry"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:contentDescription="@string/keyguard_accessibility_password"
+                android:gravity="center_horizontal"
+                android:imeOptions="flagForceAscii|actionDone"
+                android:inputType="textPassword"
+                android:maxLength="500"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textCursorDrawable="@null"
+                android:textSize="16sp"
+                android:textStyle="normal" />
+
+            <ImageView
+                android:id="@+id/switch_ime_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="end|center_vertical"
+                android:layout_marginBottom="12dp"
+                android:background="?android:attr/selectableItemBackground"
+                android:clickable="true"
+                android:contentDescription="@string/accessibility_ime_switch_button"
+                android:padding="8dip"
+                android:src="@drawable/ic_lockscreen_ime"
+                android:tint="?android:attr/textColorPrimary"
+                android:visibility="gone" />
+        </FrameLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="12dp"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+    </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
new file mode 100644
index 0000000..b562d7b
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, 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.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+     Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPatternView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_pattern_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical">
+
+    <!-- Layout here is visually identical to the previous keyguard_pattern_view.
+             I.E., 'constraints here effectively the same as the previous linear layout' -->
+    <androidx.constraintlayout.motion.widget.MotionLayout
+        android:id="@+id/pattern_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_horizontal"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical"
+        android:maxWidth = "@dimen/biometric_auth_pattern_view_max_size"
+        androidprv:layoutDescription="@xml/keyguard_pattern_scene">
+
+        <!-- Guideline need to align pattern right of centre,
+        when on small screen landscape layout -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pattern_center_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintGuide_percent="0.5" />
+
+        <LinearLayout
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:layoutDirection="ltr"
+            android:orientation="vertical"
+            androidprv:layout_constraintTop_toTopOf="parent">
+
+            <include layout="@layout/keyguard_bouncer_message_area" />
+
+            <com.android.systemui.bouncer.ui.BouncerMessageView
+                android:id="@+id/bouncer_message_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" />
+
+        </LinearLayout>
+
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pattern_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <com.android.internal.widget.LockPatternView
+            android:id="@+id/lockPatternView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="8dp"
+            androidprv:layout_constraintVertical_bias="1.0"
+            androidprv:layout_constraintDimensionRatio="1.0"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintVertical_chainStyle="packed"
+            androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@+id/lockPatternView" />
+
+    </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index 6835d59..6c79d5a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -34,6 +34,7 @@
         android:id="@+id/pin_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:maxWidth="@dimen/keyguard_security_width"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:layoutDirection="ltr"
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
new file mode 100644
index 0000000..092e10d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MotionScene
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:motion="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+    <Transition
+        motion:constraintSetStart="@id/single_constraints"
+        motion:constraintSetEnd="@+id/split_constraints"
+        motion:duration="0"
+        motion:autoTransition="none" />
+
+    <!-- No changes to default layout -->
+    <ConstraintSet android:id="@+id/single_constraints" />
+
+    <ConstraintSet android:id="@+id/split_constraints">
+
+        <Constraint
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintVertical_chainStyle="spread_inside" />
+        <Constraint
+            android:id="@+id/passwordEntry_container"
+            android:layout_width="280dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintVertical_bias="0.5"
+            androidprv:layout_constraintHorizontal_bias="0.5"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="@+id/password_center_guideline"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent"/>
+        <Constraint
+            android:id="@+id/keyguard_selector_fade_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" />
+
+    </ConstraintSet>
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
new file mode 100644
index 0000000..6112411
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MotionScene
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:motion="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+    <Transition
+        motion:constraintSetStart="@id/single_constraints"
+        motion:constraintSetEnd="@+id/split_constraints"
+        motion:duration="0"
+        motion:autoTransition="none"/>
+
+    <!-- No changes to default layout -->
+    <ConstraintSet android:id="@+id/single_constraints"/>
+
+    <ConstraintSet android:id="@+id/split_constraints">
+
+        <Constraint
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent" />
+        <Constraint
+            android:id="@+id/lockPatternView"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            androidprv:layout_constraintDimensionRatio="1.0"
+            androidprv:layout_constraintVertical_bias="0.5"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="@+id/pattern_center_guideline"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            android:layout_marginBottom="0dp" />
+        <Constraint
+            android:id="@+id/keyguard_selector_fade_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin" />
+
+    </ConstraintSet>
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
index 44af9ef..2a1270c 100644
--- a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
@@ -26,12 +26,10 @@
         motion:constraintSetStart="@id/single_constraints"
         motion:constraintSetEnd="@+id/split_constraints"
         motion:duration="0"
-        motion:autoTransition="none">
-    </Transition>
+        motion:autoTransition="none"/>
 
-    <ConstraintSet android:id="@+id/single_constraints">
-        <!-- No changes to default layout -->
-    </ConstraintSet>
+    <!-- No changes to default layout -->
+    <ConstraintSet android:id="@+id/single_constraints"/>
 
     <ConstraintSet android:id="@+id/split_constraints">
 
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index 3a15ae4..60a78d6 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -118,34 +118,37 @@
         app:layout_constraintStart_toEndOf="@id/date"
         app:layout_constraintTop_toTopOf="@id/clock" />
 
-    <LinearLayout
+    <FrameLayout
         android:id="@+id/shade_header_system_icons"
         android:layout_width="wrap_content"
         android:layout_height="@dimen/shade_header_system_icons_height"
-        android:clickable="true"
-        android:orientation="horizontal"
-        android:gravity="center_vertical"
-        android:paddingStart="@dimen/shade_header_system_icons_padding_start"
-        android:paddingEnd="@dimen/shade_header_system_icons_padding_end"
-        android:paddingTop="@dimen/shade_header_system_icons_padding_top"
-        android:paddingBottom="@dimen/shade_header_system_icons_padding_bottom"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="@id/privacy_container"
         app:layout_constraintTop_toTopOf="@id/clock">
-
-        <com.android.systemui.statusbar.phone.StatusIconContainer
-            android:id="@+id/statusIcons"
-            android:layout_width="0dp"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:paddingEnd="@dimen/signal_cluster_battery_padding" />
-
-        <com.android.systemui.battery.BatteryMeterView
-            android:id="@+id/batteryRemainingIcon"
+        <LinearLayout
+            android:id="@+id/hover_system_icons_container"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            app:textAppearance="@style/TextAppearance.QS.Status" />
-    </LinearLayout>
+            android:layout_height="match_parent"
+            android:layout_gravity="right|center_vertical"
+            android:paddingStart="@dimen/hover_system_icons_container_padding_start"
+            android:paddingEnd="@dimen/hover_system_icons_container_padding_end"
+            android:paddingTop="@dimen/hover_system_icons_container_padding_top"
+            android:paddingBottom="@dimen/hover_system_icons_container_padding_bottom">
+
+            <com.android.systemui.statusbar.phone.StatusIconContainer
+                android:id="@+id/statusIcons"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:paddingEnd="@dimen/signal_cluster_battery_padding" />
+
+            <com.android.systemui.battery.BatteryMeterView
+                android:id="@+id/batteryRemainingIcon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:textAppearance="@style/TextAppearance.QS.Status" />
+        </LinearLayout>
+    </FrameLayout>
 
     <FrameLayout
         android:id="@+id/privacy_container"
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 915dcdb..1e54fc9 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -80,10 +80,10 @@
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
-    <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
-    <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
-    <dimen name="shade_header_system_icons_padding_top">2dp</dimen>
-    <dimen name="shade_header_system_icons_padding_bottom">2dp</dimen>
+    <dimen name="hover_system_icons_container_padding_start">3dp</dimen>
+    <dimen name="hover_system_icons_container_padding_end">4dp</dimen>
+    <dimen name="hover_system_icons_container_padding_top">2dp</dimen>
+    <dimen name="hover_system_icons_container_padding_bottom">2dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index b086ed8..ae3138e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -496,10 +496,10 @@
     <dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
     <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
     <dimen name="shade_header_system_icons_height">@dimen/large_screen_shade_header_min_height</dimen>
-    <dimen name="shade_header_system_icons_padding_start">0dp</dimen>
-    <dimen name="shade_header_system_icons_padding_end">0dp</dimen>
-    <dimen name="shade_header_system_icons_padding_top">0dp</dimen>
-    <dimen name="shade_header_system_icons_padding_bottom">0dp</dimen>
+    <dimen name="hover_system_icons_container_padding_start">0dp</dimen>
+    <dimen name="hover_system_icons_container_padding_end">0dp</dimen>
+    <dimen name="hover_system_icons_container_padding_top">0dp</dimen>
+    <dimen name="hover_system_icons_container_padding_bottom">0dp</dimen>
 
     <!-- The top margin of the panel that holds the list of notifications.
          On phones it's always 0dp but it's overridden in Car UI
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 0c2341f..3cb5bc7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -59,9 +59,8 @@
      * Updates the matrix based on the provided parameters
      */
     public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-            int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
-            int taskbarSize, boolean isLargeScreen,
-            int currentRotation, boolean isRtl) {
+            int canvasWidth, int canvasHeight, boolean isLargeScreen, int currentRotation,
+            boolean isRtl) {
         boolean isRotated = false;
         boolean isOrientationDifferent;
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8200e5c..9059230 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shared.rotation;
 
 import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -37,6 +38,8 @@
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -67,6 +70,7 @@
 
 import java.io.PrintWriter;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 /**
@@ -119,6 +123,8 @@
     private final int mIconCwStart0ResId;
     @DrawableRes
     private final int mIconCwStart90ResId;
+    /** Defaults to mainExecutor if not set via {@link #setBgExecutor(Executor)}. */
+    private Executor mBgExecutor;
 
     @DrawableRes
     private int mIconResId;
@@ -178,6 +184,8 @@
         mAccessibilityManager = AccessibilityManager.getInstance(context);
         mTaskStackListener = new TaskStackListenerImpl();
         mWindowRotationProvider = windowRotationProvider;
+
+        mBgExecutor = context.getMainExecutor();
     }
 
     public void setRotationButton(RotationButton rotationButton,
@@ -193,6 +201,10 @@
         return mContext;
     }
 
+    public void setBgExecutor(Executor bgExecutor) {
+        mBgExecutor = bgExecutor;
+    }
+
     /**
      * Called during Taskbar initialization.
      */
@@ -219,8 +231,11 @@
 
         mListenersRegistered = true;
 
-        updateDockedState(mContext.registerReceiver(mDockedReceiver,
-                new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+        mBgExecutor.execute(() -> {
+            final Intent intent = mContext.registerReceiver(mDockedReceiver,
+                    new IntentFilter(Intent.ACTION_DOCK_EVENT));
+            mContext.getMainExecutor().execute(() -> updateDockedState(intent));
+        });
 
         if (registerRotationWatcher) {
             try {
@@ -246,11 +261,13 @@
 
         mListenersRegistered = false;
 
-        try {
-            mContext.unregisterReceiver(mDockedReceiver);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Docked receiver already unregistered", e);
-        }
+        mBgExecutor.execute(() -> {
+            try {
+                mContext.unregisterReceiver(mDockedReceiver);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Docked receiver already unregistered", e);
+            }
+        });
 
         if (mRotationWatcherRegistered) {
             try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 8738d33..abd1563 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,8 +16,6 @@
 
 package com.android.keyguard;
 
-import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
-
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -257,10 +255,8 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
                         mFalsingCollector, mKeyguardViewController,
-                        mFeatureFlags);
+                        mDevicePostureController, mFeatureFlags);
             } else if (keyguardInputView instanceof KeyguardPINView) {
-                ((KeyguardPINView) keyguardInputView).setIsLockScreenLandscapeEnabled(
-                        mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index bb3e759..72b4ae5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
@@ -105,21 +106,21 @@
     }
 
     void onDevicePostureChanged(@DevicePostureInt int posture) {
-        if (mLastDevicePosture != posture) {
-            mLastDevicePosture = posture;
+        if (mLastDevicePosture == posture) return;
+        mLastDevicePosture = posture;
 
-            if (mIsLockScreenLandscapeEnabled) {
-                boolean useSplitBouncerAfterFold =
-                        mLastDevicePosture == DEVICE_POSTURE_CLOSED
-                        &&  getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+        if (mIsLockScreenLandscapeEnabled) {
+            boolean useSplitBouncerAfterFold =
+                    mLastDevicePosture == DEVICE_POSTURE_CLOSED
+                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+                    && getResources().getBoolean(R.bool.update_bouncer_constraints);
 
-                if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
-                    updateConstraints(useSplitBouncerAfterFold);
-                }
+            if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+                updateConstraints(useSplitBouncerAfterFold);
             }
-
-            updateMargins();
         }
+
+        updateMargins();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 59ee0d8..8d5fc04 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.view.WindowInsets.Type.ime;
 
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
@@ -27,10 +28,13 @@
 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 static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Insets;
@@ -46,12 +50,16 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.constraintlayout.motion.widget.MotionLayout;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+
+
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
  * an unlock password
@@ -68,10 +76,14 @@
 
     private TextView mPasswordEntry;
     private TextViewInputDisabler mPasswordEntryDisabler;
-
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
     private DisappearAnimationListener mDisappearAnimationListener;
+    @Nullable private MotionLayout mContainerMotionLayout;
+    private boolean mAlreadyUsingSplitBouncer = false;
+    private boolean mIsLockScreenLandscapeEnabled = false;
+    @DevicePostureController.DevicePostureInt
+    private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
     private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
     private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
 
@@ -89,6 +101,21 @@
                 context, android.R.interpolator.fast_out_linear_in);
     }
 
+    /**
+     * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+     * enabled
+     */
+    public void setIsLockScreenLandscapeEnabled() {
+        mIsLockScreenLandscapeEnabled = true;
+        findContainerLayout();
+    }
+
+    private void findContainerLayout() {
+        if (mIsLockScreenLandscapeEnabled) {
+            mContainerMotionLayout = findViewById(R.id.password_container);
+        }
+    }
+
     @Override
     protected void resetState() {
     }
@@ -124,6 +151,35 @@
         }
     }
 
+    void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+        if (mLastDevicePosture == posture) return;
+        mLastDevicePosture = posture;
+
+        if (mIsLockScreenLandscapeEnabled) {
+            boolean useSplitBouncerAfterFold =
+                    mLastDevicePosture == DEVICE_POSTURE_CLOSED
+                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+                    && getResources().getBoolean(R.bool.update_bouncer_constraints);
+
+            if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+                updateConstraints(useSplitBouncerAfterFold);
+            }
+        }
+
+    }
+
+    @Override
+    protected void updateConstraints(boolean useSplitBouncer) {
+        mAlreadyUsingSplitBouncer = useSplitBouncer;
+        if (useSplitBouncer) {
+            mContainerMotionLayout.jumpToState(R.id.split_constraints);
+            mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
+        } else {
+            mContainerMotionLayout.jumpToState(R.id.single_constraints);
+            mContainerMotionLayout.setMaxWidth(getResources()
+                    .getDimensionPixelSize(R.dimen.keyguard_security_width));
+        }
+    }
 
     @Override
     protected void onFinishInflate() {
@@ -131,6 +187,11 @@
 
         mPasswordEntry = findViewById(getPasswordTextViewId());
         mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
+
+        // EditText cursor can fail screenshot tests, so disable it when testing
+        if (ActivityManager.isRunningInTestHarness()) {
+            mPasswordEntry.setCursorVisible(false);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 5dbd014..ab8cd53 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.content.res.Resources;
 import android.os.UserHandle;
 import android.text.Editable;
@@ -41,6 +43,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.List;
@@ -49,8 +52,10 @@
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
     private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
-
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+    private final DevicePostureController mPostureController;
+    private final DevicePostureController.Callback mPostureCallback = posture ->
+            mView.onDevicePostureChanged(posture);
     private final InputMethodManager mInputMethodManager;
     private final DelayableExecutor mMainExecutor;
     private final KeyguardViewController mKeyguardViewController;
@@ -106,14 +111,19 @@
             @Main Resources resources,
             FalsingCollector falsingCollector,
             KeyguardViewController keyguardViewController,
+            DevicePostureController postureController,
             FeatureFlags featureFlags) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags);
         mKeyguardSecurityCallback = keyguardSecurityCallback;
         mInputMethodManager = inputMethodManager;
+        mPostureController = postureController;
         mMainExecutor = mainExecutor;
         mKeyguardViewController = keyguardViewController;
+        if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
+            view.setIsLockScreenLandscapeEnabled();
+        }
         mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
         mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -127,6 +137,9 @@
         mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
                 | InputType.TYPE_TEXT_VARIATION_PASSWORD);
 
+        mView.onDevicePostureChanged(mPostureController.getDevicePosture());
+        mPostureController.addCallback(mPostureCallback);
+
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
         mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
@@ -164,6 +177,7 @@
     protected void onViewDetached() {
         super.onViewDetached();
         mPasswordEntry.setOnEditorActionListener(null);
+        mPostureController.removeCallback(mPostureCallback);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 2bdf46e..56706b5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,9 +15,13 @@
  */
 package com.android.keyguard;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -29,6 +33,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import androidx.constraintlayout.motion.widget.MotionLayout;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
@@ -75,7 +80,10 @@
 
     BouncerKeyguardMessageArea mSecurityMessageDisplay;
     private View mEcaView;
-    private ConstraintLayout mContainer;
+    @Nullable private MotionLayout mContainerMotionLayout;
+    @Nullable private ConstraintLayout mContainerConstraintLayout;
+    private boolean mAlreadyUsingSplitBouncer = false;
+    private boolean mIsLockScreenLandscapeEnabled = false;
     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
 
     public KeyguardPatternView(Context context) {
@@ -98,16 +106,44 @@
                 mContext, android.R.interpolator.fast_out_linear_in));
     }
 
+    /**
+     * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+     * enabled, instead of constraint layout (old bouncer implementation)
+     */
+    public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
+        mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
+        findContainerLayout();
+    }
+
+    private void findContainerLayout() {
+        if (mIsLockScreenLandscapeEnabled) {
+            mContainerMotionLayout = findViewById(R.id.pattern_container);
+        } else {
+            mContainerConstraintLayout = findViewById(R.id.pattern_container);
+        }
+    }
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         updateMargins();
     }
 
     void onDevicePostureChanged(@DevicePostureInt int posture) {
-        if (mLastDevicePosture != posture) {
-            mLastDevicePosture = posture;
-            updateMargins();
+        if (mLastDevicePosture == posture) return;
+        mLastDevicePosture = posture;
+
+        if (mIsLockScreenLandscapeEnabled) {
+            boolean useSplitBouncerAfterFold =
+                    mLastDevicePosture == DEVICE_POSTURE_CLOSED
+                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+                    && getResources().getBoolean(R.bool.update_bouncer_constraints);
+
+            if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+                updateConstraints(useSplitBouncerAfterFold);
+            }
         }
+
+        updateMargins();
     }
 
     private void updateMargins() {
@@ -115,12 +151,36 @@
         float halfOpenPercentage =
                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
 
-        ConstraintSet cs = new ConstraintSet();
-        cs.clone(mContainer);
-        cs.setGuidelinePercent(R.id.pattern_top_guideline,
-                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
-                ? halfOpenPercentage : 0.0f);
-        cs.applyTo(mContainer);
+        if (mIsLockScreenLandscapeEnabled) {
+            ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints);
+            cs.setGuidelinePercent(R.id.pattern_top_guideline,
+                    mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+            cs.applyTo(mContainerMotionLayout);
+        } else {
+            ConstraintSet cs = new ConstraintSet();
+            cs.clone(mContainerConstraintLayout);
+            cs.setGuidelinePercent(R.id.pattern_top_guideline,
+                    mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+            cs.applyTo(mContainerConstraintLayout);
+        }
+    }
+
+    /**
+     * Updates the keyguard view's constraints (single or split constraints).
+     * Split constraints are only used for small landscape screens.
+     * Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled.
+     */
+    @Override
+    protected void updateConstraints(boolean useSplitBouncer) {
+        mAlreadyUsingSplitBouncer = useSplitBouncer;
+        if (useSplitBouncer) {
+            mContainerMotionLayout.jumpToState(R.id.split_constraints);
+            mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
+        } else {
+            mContainerMotionLayout.jumpToState(R.id.single_constraints);
+            mContainerMotionLayout.setMaxWidth(getResources()
+                    .getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size));
+        }
     }
 
     @Override
@@ -130,7 +190,6 @@
         mLockPatternView = findViewById(R.id.lockPatternView);
 
         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
-        mContainer = findViewById(R.id.pattern_container);
     }
 
     @Override
@@ -209,7 +268,7 @@
                 getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR));
 
         DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
-                        ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
+                ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
         disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                 () -> {
                     enableClipping(true);
@@ -220,7 +279,7 @@
         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
             mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
                     (long) (200 * durationMultiplier),
-                    - mDisappearAnimationUtils.getStartTranslation() * 3,
+                    -mDisappearAnimationUtils.getStartTranslation() * 3,
                     false /* appearing */,
                     mDisappearAnimationUtils.getInterpolator(),
                     null /* finishRunnable */);
@@ -229,9 +288,16 @@
     }
 
     private void enableClipping(boolean enable) {
-        setClipChildren(enable);
-        mContainer.setClipToPadding(enable);
-        mContainer.setClipChildren(enable);
+        if (mContainerConstraintLayout != null) {
+            setClipChildren(enable);
+            mContainerConstraintLayout.setClipToPadding(enable);
+            mContainerConstraintLayout.setClipChildren(enable);
+        }
+        if (mContainerMotionLayout != null) {
+            setClipChildren(enable);
+            mContainerMotionLayout.setClipToPadding(enable);
+            mContainerMotionLayout.setClipChildren(enable);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index a30b447..98312b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 
 import android.content.res.ColorStateList;
 import android.os.AsyncTask;
@@ -205,6 +206,8 @@
         mLatencyTracker = latencyTracker;
         mFalsingCollector = falsingCollector;
         mEmergencyButtonController = emergencyButtonController;
+        view.setIsLockScreenLandscapeEnabled(
+                featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
         mLockPatternView = mView.findViewById(R.id.lockPatternView);
         mPostureController = postureController;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 574a059..0af803f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.view.View;
 
 import com.android.internal.util.LatencyTracker;
@@ -61,6 +63,7 @@
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
         mFeatureFlags = featureFlags;
+        view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
         mBackspaceKey = view.findViewById(R.id.delete_button);
         mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 4cc90c2..f18504c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -17,6 +17,7 @@
 package com.android.keyguard;
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 
 import android.util.Log;
@@ -154,9 +155,9 @@
     private int getLayoutIdFor(SecurityMode securityMode) {
         // TODO (b/297863911, b/297864907) - implement motion layout for other bouncers
         switch (securityMode) {
-            case Pattern: return R.layout.keyguard_pattern_view;
+            case Pattern: return R.layout.keyguard_pattern_motion_layout;
             case PIN: return R.layout.keyguard_pin_motion_layout;
-            case Password: return R.layout.keyguard_password_view;
+            case Password: return R.layout.keyguard_password_motion_layout;
             case SimPin: return R.layout.keyguard_sim_pin_view;
             case SimPuk: return R.layout.keyguard_sim_puk_view;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e4bbd3a..0f733c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -35,6 +35,7 @@
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
 import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
+
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 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;
@@ -4201,7 +4202,7 @@
                         WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
                 final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
                 mAllowFingerprintOnCurrentOccludingActivity =
-                        standardTask.topActivity != null
+                        standardTask != null && standardTask.topActivity != null
                                 && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
                                 && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
                                         standardTask.topActivity.getPackageName())
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index c9801d7..9fd0602 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -54,6 +54,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -84,6 +85,7 @@
     private final SystemClock mClock;
 
     private H mBGHandler;
+    private final Executor mBgExecutor;
     private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
     private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>();
     private boolean mListening;
@@ -153,6 +155,7 @@
     public AppOpsControllerImpl(
             Context context,
             @Background Looper bgLooper,
+            @Background Executor bgExecutor,
             DumpManager dumpManager,
             AudioManager audioManager,
             IndividualSensorPrivacyController sensorPrivacyController,
@@ -162,6 +165,7 @@
         mDispatcher = dispatcher;
         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mBGHandler = new H(bgLooper);
+        mBgExecutor = bgExecutor;
         final int numOps = OPS.length;
         for (int i = 0; i < numOps; i++) {
             mCallbacksByCode.put(OPS[i], new ArraySet<>());
@@ -184,41 +188,43 @@
     @VisibleForTesting
     protected void setListening(boolean listening) {
         mListening = listening;
-        if (listening) {
-            // System UI could be restarted while ops are active, so fetch the currently active ops
-            // once System UI starts listening again.
-            fetchCurrentActiveOps();
+        // Move IPCs to the background.
+        mBgExecutor.execute(() -> {
+            if (listening) {
+                // System UI could be restarted while ops are active, so fetch the currently active
+                // ops once System UI starts listening again -- see b/294104969.
+                fetchCurrentActiveOps();
 
-            mAppOps.startWatchingActive(OPS, this);
-            mAppOps.startWatchingNoted(OPS, this);
-            mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
-            mSensorPrivacyController.addCallback(this);
+                mAppOps.startWatchingActive(OPS, this);
+                mAppOps.startWatchingNoted(OPS, this);
+                mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
+                mSensorPrivacyController.addCallback(this);
 
-            mMicMuted = mAudioManager.isMicrophoneMute()
-                    || mSensorPrivacyController.isSensorBlocked(MICROPHONE);
-            mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA);
+                mMicMuted = mAudioManager.isMicrophoneMute()
+                        || mSensorPrivacyController.isSensorBlocked(MICROPHONE);
+                mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA);
 
-            mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
-                    mAudioManager.getActiveRecordingConfigurations()));
-            mDispatcher.registerReceiverWithHandler(this,
-                    new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler);
+                mBGHandler.post(() -> mAudioRecordingCallback.onRecordingConfigChanged(
+                        mAudioManager.getActiveRecordingConfigurations()));
+                mDispatcher.registerReceiverWithHandler(this,
+                        new IntentFilter(ACTION_MICROPHONE_MUTE_CHANGED), mBGHandler);
+            } else {
+                mAppOps.stopWatchingActive(this);
+                mAppOps.stopWatchingNoted(this);
+                mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+                mSensorPrivacyController.removeCallback(this);
 
-        } else {
-            mAppOps.stopWatchingActive(this);
-            mAppOps.stopWatchingNoted(this);
-            mAudioManager.unregisterAudioRecordingCallback(mAudioRecordingCallback);
-            mSensorPrivacyController.removeCallback(this);
-
-            mBGHandler.removeCallbacksAndMessages(null); // null removes all
-            mDispatcher.unregisterReceiver(this);
-            synchronized (mActiveItems) {
-                mActiveItems.clear();
-                mRecordingsByUid.clear();
+                mBGHandler.removeCallbacksAndMessages(null); // null removes all
+                mDispatcher.unregisterReceiver(this);
+                synchronized (mActiveItems) {
+                    mActiveItems.clear();
+                    mRecordingsByUid.clear();
+                }
+                synchronized (mNotedItems) {
+                    mNotedItems.clear();
+                }
             }
-            synchronized (mNotedItems) {
-                mNotedItems.clear();
-            }
-        }
+        });
     }
 
     private void fetchCurrentActiveOps() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 97b0617..054bd08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -25,7 +25,7 @@
             shadeExpansionCollectorJob =
                 scope.launch {
                     // wait for it to emit true once
-                    shadeInteractorLazy.get().anyExpanding.first { it }
+                    shadeInteractorLazy.get().isAnyExpanding.first { it }
                     onShadeInteraction.run()
                 }
             shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index f12b919..d89332d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -49,6 +49,7 @@
 import com.android.systemui.power.PowerUI
 import com.android.systemui.reardisplay.RearDisplayDialogController
 import com.android.systemui.recents.Recents
+import com.android.systemui.recents.ScreenPinningRequest
 import com.android.systemui.settings.dagger.MultiUserUtilsModule
 import com.android.systemui.shortcut.ShortcutKeyDispatcher
 import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -366,4 +367,9 @@
     @IntoMap
     @ClassKey(KeyguardDismissBinder::class)
     abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(ScreenPinningRequest::class)
+    abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 1751358..4f16685 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.display.data.repository
 
 import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
 import android.hardware.display.DisplayManager.DisplayListener
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
@@ -95,28 +96,36 @@
     @Background backgroundCoroutineDispatcher: CoroutineDispatcher
 ) : DisplayRepository {
     // Displays are enabled only after receiving them in [onDisplayAdded]
-    private val allDisplayEvents: Flow<DisplayEvent> = conflatedCallbackFlow {
-        val callback =
-            object : DisplayListener {
-                override fun onDisplayAdded(displayId: Int) {
-                    trySend(DisplayEvent.Added(displayId))
-                }
+    private val allDisplayEvents: Flow<DisplayEvent> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DisplayListener {
+                        override fun onDisplayAdded(displayId: Int) {
+                            trySend(DisplayEvent.Added(displayId))
+                        }
 
-                override fun onDisplayRemoved(displayId: Int) {
-                    trySend(DisplayEvent.Removed(displayId))
-                }
+                        override fun onDisplayRemoved(displayId: Int) {
+                            trySend(DisplayEvent.Removed(displayId))
+                        }
 
-                override fun onDisplayChanged(displayId: Int) {
-                    trySend(DisplayEvent.Changed(displayId))
-                }
+                        override fun onDisplayChanged(displayId: Int) {
+                            trySend(DisplayEvent.Changed(displayId))
+                        }
+                    }
+                // Triggers an initial event when subscribed. This is needed to avoid getDisplays to
+                // be called when this class is constructed, but only when someone subscribes to
+                // this flow.
+                trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY))
+                displayManager.registerDisplayListener(
+                    callback,
+                    backgroundHandler,
+                    EVENT_FLAG_DISPLAY_ADDED or
+                        EVENT_FLAG_DISPLAY_CHANGED or
+                        EVENT_FLAG_DISPLAY_REMOVED,
+                )
+                awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
-        displayManager.registerDisplayListener(
-            callback,
-            backgroundHandler,
-            EVENT_FLAG_DISPLAY_ADDED or EVENT_FLAG_DISPLAY_CHANGED or EVENT_FLAG_DISPLAY_REMOVED,
-        )
-        awaitClose { displayManager.unregisterDisplayListener(callback) }
-    }
+            .flowOn(backgroundCoroutineDispatcher)
 
     override val displayChangeEvent: Flow<Int> =
         allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId }
@@ -128,7 +137,9 @@
             .stateIn(
                 applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = getDisplays()
+                // To avoid getting displays on this object construction, they are get after the
+                // first event. allDisplayEvents emits a changed event when we subscribe to it.
+                initialValue = emptySet()
             )
 
     private fun getDisplays(): Set<Display> =
@@ -146,12 +157,23 @@
 
     private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
 
+    private fun getInitialConnectedDisplays(): Set<Int> =
+        displayManager
+            .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+            .map { it.displayId }
+            .toSet()
+            .also {
+                if (DEBUG) {
+                    Log.d(TAG, "getInitialConnectedDisplays: $it")
+                }
+            }
+
     /* keeps connected displays until they are disconnected. */
     private val connectedDisplayIds: StateFlow<Set<Int>> =
         conflatedCallbackFlow {
+                val connectedIds = getInitialConnectedDisplays().toMutableSet()
                 val callback =
                     object : DisplayConnectionListener {
-                        private val connectedIds = mutableSetOf<Int>()
                         override fun onDisplayConnected(id: Int) {
                             if (DEBUG) {
                                 Log.d(TAG, "display with id=$id connected.")
@@ -170,6 +192,7 @@
                             trySend(connectedIds.toSet())
                         }
                     }
+                trySend(connectedIds.toSet())
                 displayManager.registerDisplayListener(
                     callback,
                     backgroundHandler,
@@ -183,6 +206,10 @@
             .stateIn(
                 applicationScope,
                 started = SharingStarted.WhileSubscribed(),
+                // The initial value is set to empty, but connected displays are gathered as soon as
+                // the flow starts being collected. This is to ensure the call to get displays (an
+                // IPC) happens in the background instead of when this object
+                // is instantiated.
                 initialValue = emptySet()
             )
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 37a9572..4d713a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -107,7 +107,7 @@
 
     // TODO(b/268005230): Tracking Bug
     @JvmField
-    val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true)
+    val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
 
     // TODO(b/280783617): Tracking Bug
     @Keep
@@ -115,7 +115,7 @@
     val BUILDER_EXTRAS_OVERRIDE =
         sysPropBooleanFlag(
             "persist.sysui.notification.builder_extras_override",
-            default = false
+            default = true
         )
 
     /** Only notify group expansion listeners when a change happens. */
@@ -182,15 +182,12 @@
 
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
-    @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag("face_auth_refactor", teamfood = true)
+    @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
 
     /** Flag to control the revamp of keyguard biometrics progress animation */
     // TODO(b/244313043): Tracking bug
     @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
 
-    // TODO(b/262780002): Tracking Bug
-    @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui")
-
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
     val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
@@ -294,6 +291,11 @@
     // TODO(b/291767565): Tracking bug.
     @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view")
 
+    /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */
+    // TODO(b/299115332): Tracking Bug.
+    @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW =
+        unreleasedFlag("migrate_keyguard_status_bar_view")
+
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
@@ -318,6 +320,11 @@
             R.bool.flag_stop_pulsing_face_scanning_animation,
             "stop_pulsing_face_scanning_animation")
 
+    /** Flag to use a separate view for the alternate bouncer. */
+    // TODO(b/300440924): Tracking bug
+    @JvmField
+    val ALTERNATE_BOUNCER_REFACTOR: UnreleasedFlag = unreleasedFlag("alternate_bouncer_view")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -403,7 +410,7 @@
 
     // TODO(b/290676905): Tracking Bug
     val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS =
-        unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true)
+        releasedFlag("new_shade_carrier_group_mobile_icons")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -766,14 +773,14 @@
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
-    val USE_REPOS_FOR_BOUNCER_SHOWING =
-        unreleasedFlag("use_repos_for_bouncer_showing", teamfood = true)
+    val USE_REPOS_FOR_BOUNCER_SHOWING = releasedFlag("use_repos_for_bouncer_showing")
 
     // 3100 - Haptic interactions
 
     // TODO(b/290213663): Tracking Bug
     @JvmField
-    val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
+    val ONE_WAY_HAPTICS_API_MIGRATION =
+            unreleasedFlag("oneway_haptics_api_migration", teamfood = true)
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
@@ -803,4 +810,9 @@
     /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
     @JvmField
     val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
+
+    // TODO(b/300995746): Tracking Bug
+    /** Enable communal hub features. */
+    @JvmField
+    val COMMUNAL_HUB = resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_hub")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9915720..9241776 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -764,13 +764,6 @@
                 }
             }
         }
-
-        @Override
-        public void onStrongAuthStateChanged(int userId) {
-            if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
-                doKeyguardLocked(null);
-            }
-        }
     };
 
     ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -2193,9 +2186,8 @@
      * Enable the keyguard if the settings are appropriate.
      */
     private void doKeyguardLocked(Bundle options) {
-        // if another app is disabling us, don't show unless we're in lockdown mode
-        if (!mExternallyEnabled
-                && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+        // if another app is disabling us, don't show
+        if (!mExternallyEnabled) {
             if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
 
             mNeedToReshowWhenReenabled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 5d7a3d4..23f50ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
+import com.android.systemui.wallet.util.getPaymentCards
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -60,7 +61,7 @@
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
                     override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
-                        val hasCards = response?.walletCards?.isNotEmpty() == true
+                        val hasCards = getPaymentCards(response.walletCards)?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
                                 isFeatureEnabled = isWalletAvailable(),
@@ -135,7 +136,7 @@
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
                     override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         continuation.resumeWith(
-                            Result.success(response?.walletCards ?: emptyList())
+                            Result.success(getPaymentCards(response.walletCards) ?: emptyList())
                         )
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 6db21b2..233acd9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -54,13 +54,13 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
+import com.google.errorprone.annotations.CompileTimeConstant
 import java.io.PrintWriter
 import java.util.Arrays
 import java.util.stream.Collectors
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
@@ -112,33 +112,27 @@
     fun setLockedOut(isLockedOut: Boolean)
 
     /**
-     * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
-     * invoked.
-     */
-    fun pauseFaceAuth()
-
-    /**
-     * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
-     * [authenticate] will run as long as other gating conditions don't stop it from running.
-     */
-    fun resumeFaceAuth()
-
-    /**
-     * Trigger face authentication.
+     * Request face authentication or detection to be run.
      *
      * [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
      * ignored if face authentication is already running. Results should be propagated through
      * [authenticationStatus]
      *
      * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+     *
+     * Method returns immediately and the face auth request is processed as soon as possible.
      */
-    suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
+    fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
 
     /** Stop currently running face authentication or detection. */
     fun cancel()
 }
 
-@OptIn(ExperimentalCoroutinesApi::class)
+private data class AuthenticationRequest(
+    val uiEvent: FaceAuthUiEvent,
+    val fallbackToDetection: Boolean
+)
+
 @SysUISingleton
 class DeviceEntryFaceAuthRepositoryImpl
 @Inject
@@ -171,6 +165,8 @@
     private var faceAcquiredInfoIgnoreList: Set<Int>
     private var retryCount = 0
 
+    private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
+
     private var cancelNotReceivedHandlerJob: Job? = null
     private var halErrorRetryJob: Job? = null
 
@@ -193,15 +189,6 @@
     override val isAuthRunning: StateFlow<Boolean>
         get() = _isAuthRunning
 
-    private val faceAuthPaused = MutableStateFlow(false)
-    override fun pauseFaceAuth() {
-        faceAuthPaused.value = true
-    }
-
-    override fun resumeFaceAuth() {
-        faceAuthPaused.value = false
-    }
-
     private val keyguardSessionId: InstanceId?
         get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
 
@@ -213,6 +200,8 @@
     override val isAuthenticated: Flow<Boolean>
         get() = _isAuthenticated
 
+    private var cancellationInProgress = MutableStateFlow(false)
+
     override val isBypassEnabled: Flow<Boolean> =
         keyguardBypassController?.let {
             conflatedCallbackFlow {
@@ -302,6 +291,7 @@
             observeFaceDetectGatingChecks()
             observeFaceAuthResettingConditions()
             listenForSchedulingWatchdog()
+            processPendingAuthRequests()
         } else {
             canRunFaceAuth = MutableStateFlow(false).asStateFlow()
             canRunDetection = MutableStateFlow(false).asStateFlow()
@@ -338,6 +328,7 @@
             )
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
+                    clearPendingAuthRequest("Resetting auth status")
                     _isAuthenticated.value = false
                     retryCount = 0
                     halErrorRetryJob?.cancel()
@@ -346,6 +337,15 @@
             .launchIn(applicationScope)
     }
 
+    private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) {
+        faceAuthLogger.clearingPendingAuthRequest(
+            loggingContext,
+            pendingAuthenticateRequest.value?.uiEvent,
+            pendingAuthenticateRequest.value?.fallbackToDetection
+        )
+        pendingAuthenticateRequest.value = null
+    }
+
     private fun observeFaceDetectGatingChecks() {
         canRunDetection
             .onEach {
@@ -378,7 +378,6 @@
                 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                 "isFaceAuthEnrolledAndEnabled"
             ),
-            Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
             Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
             Pair(
                 keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
@@ -402,7 +401,13 @@
                 biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
                 "userHasNotLockedDownDevice"
             ),
-            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
+            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"),
+            Pair(
+                userRepository.selectedUser
+                    .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }
+                    .isFalse(),
+                "userSwitchingInProgress"
+            )
         )
     }
 
@@ -440,9 +445,6 @@
                 }
                 _authenticationStatus.value = errorStatus
                 _isAuthenticated.value = false
-                if (errorStatus.isCancellationError()) {
-                    handleFaceCancellationError()
-                }
                 if (errorStatus.isHardwareError()) {
                     faceAuthLogger.hardwareError(errorStatus)
                     handleFaceHardwareError()
@@ -471,16 +473,6 @@
             }
         }
 
-    private fun handleFaceCancellationError() {
-        applicationScope.launch {
-            faceAuthRequestedWhileCancellation?.let {
-                faceAuthLogger.launchingQueuedFaceAuthRequest(it)
-                authenticate(it)
-            }
-            faceAuthRequestedWhileCancellation = null
-        }
-    }
-
     private fun handleFaceHardwareError() {
         if (retryCount < HAL_ERROR_RETRY_MAX) {
             retryCount++
@@ -490,7 +482,7 @@
                     delay(HAL_ERROR_RETRY_TIMEOUT)
                     if (retryCount < HAL_ERROR_RETRY_MAX) {
                         faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
-                        authenticate(
+                        requestAuthenticate(
                             FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
                             fallbackToDetection = false
                         )
@@ -501,7 +493,7 @@
 
     private fun onFaceAuthRequestCompleted() {
         cancelNotReceivedHandlerJob?.cancel()
-        cancellationInProgress = false
+        cancellationInProgress.value = false
         _isAuthRunning.value = false
         authCancellationSignal = null
     }
@@ -512,24 +504,60 @@
             _detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
         }
 
-    private var cancellationInProgress = false
-    private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
+    override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        if (pendingAuthenticateRequest.value != null) {
+            faceAuthLogger.ignoredFaceAuthTrigger(
+                pendingAuthenticateRequest.value?.uiEvent,
+                "Previously queued trigger skipped due to new request"
+            )
+        }
+        faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection)
+        pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection)
+    }
 
-    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+    private fun processPendingAuthRequests() {
+        combine(
+                pendingAuthenticateRequest,
+                canRunFaceAuth,
+                canRunDetection,
+                cancellationInProgress,
+            ) { pending, canRunAuth, canRunDetect, cancelInProgress ->
+                if (
+                    pending != null &&
+                        !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) ||
+                        cancelInProgress
+                ) {
+                    faceAuthLogger.notProcessingRequestYet(
+                        pending?.uiEvent,
+                        canRunAuth,
+                        canRunDetect,
+                        cancelInProgress
+                    )
+                    return@combine null
+                } else {
+                    return@combine pending
+                }
+            }
+            .onEach {
+                it?.let {
+                    faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection)
+                    clearPendingAuthRequest("Authenticate was invoked")
+                    authenticate(it.uiEvent, it.fallbackToDetection)
+                }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
+    private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         if (_isAuthRunning.value) {
             faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
             return
         }
 
-        if (cancellationInProgress) {
-            faceAuthLogger.queuingRequestWhileCancelling(
-                faceAuthRequestedWhileCancellation,
-                uiEvent
-            )
-            faceAuthRequestedWhileCancellation = uiEvent
+        if (cancellationInProgress.value) {
+            faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress")
             return
-        } else {
-            faceAuthRequestedWhileCancellation = null
         }
 
         if (canRunFaceAuth.value) {
@@ -553,12 +581,19 @@
                     FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
                 )
             }
-        } else if (fallbackToDetection && canRunDetection.value) {
-            faceAuthLogger.ignoredFaceAuthTrigger(
-                uiEvent,
-                "face auth gating check is false, falling back to detection."
-            )
-            detect()
+        } else if (canRunDetection.value) {
+            if (fallbackToDetection) {
+                faceAuthLogger.ignoredFaceAuthTrigger(
+                    uiEvent,
+                    "face auth gating check is false, falling back to detection."
+                )
+                detect()
+            } else {
+                faceAuthLogger.ignoredFaceAuthTrigger(
+                    uiEvent = uiEvent,
+                    "face auth gating check is false and fallback to detection is not requested"
+                )
+            }
         } else {
             faceAuthLogger.ignoredFaceAuthTrigger(
                 uiEvent,
@@ -608,13 +643,13 @@
                 faceAuthLogger.cancelSignalNotReceived(
                     _isAuthRunning.value,
                     _isLockedOut.value,
-                    cancellationInProgress,
-                    faceAuthRequestedWhileCancellation
+                    cancellationInProgress.value,
+                    pendingAuthenticateRequest.value?.uiEvent
                 )
                 _authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
                 onFaceAuthRequestCompleted()
             }
-        cancellationInProgress = true
+        cancellationInProgress.value = true
         _isAuthRunning.value = false
     }
 
@@ -647,9 +682,7 @@
             "    supportsFaceDetection: " +
                 "${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
         )
-        pw.println(
-            "  faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
-        )
+        pw.println("  _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
         pw.println("  authCancellationSignal: $authCancellationSignal")
         pw.println("  detectCancellationSignal: $detectCancellationSignal")
         pw.println("  faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index 46135fa..c8cb9e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -56,9 +56,6 @@
         get() = emptyFlow()
 
     override fun setLockedOut(isLockedOut: Boolean) = Unit
-    override fun pauseFaceAuth() = Unit
-
-    override fun resumeFaceAuth() = Unit
 
     /**
      * Trigger face authentication.
@@ -69,7 +66,7 @@
      *
      * Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
      */
-    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {}
+    override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) = Unit
 
     /** Stop currently running face authentication or detection. */
     override fun cancel() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
index ca430da..a257f52 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -172,7 +172,6 @@
 
     private fun isFeatureEnabled(): Boolean {
         return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
-            featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) &&
             appContext.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9c796f8..7f43cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -380,10 +380,6 @@
     suspend fun getPickerFlags(): List<KeyguardPickerFlag> {
         return listOf(
             KeyguardPickerFlag(
-                name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
-                value = featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI),
-            ),
-            KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value =
                     !isFeatureDisabledByDevicePolicy() &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index ccc2080..f0df3a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -52,8 +52,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 import kotlinx.coroutines.yield
 
 /**
@@ -144,14 +142,11 @@
             .onEach { (previous, curr) ->
                 val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
                 val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
-                if (!wasSwitching && isSwitching) {
-                    repository.pauseFaceAuth()
-                } else if (wasSwitching && !isSwitching) {
+                if (wasSwitching && !isSwitching) {
                     val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
                     repository.setLockedOut(
                         lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
                     )
-                    repository.resumeFaceAuth()
                     yield()
                     runFaceAuth(
                         FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -232,12 +227,8 @@
                     )
             } else {
                 faceAuthenticationStatusOverride.value = null
-                applicationScope.launch {
-                    withContext(mainDispatcher) {
-                        faceAuthenticationLogger.authRequested(uiEvent)
-                        repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
-                    }
-                }
+                faceAuthenticationLogger.authRequested(uiEvent)
+                repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect)
             }
         } else {
             faceAuthenticationLogger.ignoredFaceAuthTrigger(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 15bb909..43bbf69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
@@ -49,6 +50,7 @@
     defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     defaultStatusViewSection: DefaultStatusViewSection,
+    defaultStatusBarSection: DefaultStatusBarSection,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
     splitShadeGuidelines: SplitShadeGuidelines,
     aodNotificationIconsSection: AodNotificationIconsSection,
@@ -64,6 +66,7 @@
             defaultAmbientIndicationAreaSection,
             defaultSettingsPopupMenuSection,
             defaultStatusViewSection,
+            defaultStatusBarSection,
             defaultNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 6534dcf..a9885fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
@@ -41,6 +42,7 @@
     defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
     defaultStatusViewSection: DefaultStatusViewSection,
+    defaultStatusBarSection: DefaultStatusBarSection,
     splitShadeGuidelines: SplitShadeGuidelines,
     defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
     aodNotificationIconsSection: AodNotificationIconsSection,
@@ -55,6 +57,7 @@
             defaultSettingsPopupMenuSection,
             alignShortcutsToUdfpsSection,
             defaultStatusViewSection,
+            defaultStatusBarSection,
             defaultNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
new file mode 100644
index 0000000..d6c69b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 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.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/** A section for the status bar displayed at the top of the lockscreen. */
+class DefaultStatusBarSection
+@Inject
+constructor(
+    private val context: Context,
+    private val featureFlags: FeatureFlags,
+    private val notificationPanelView: NotificationPanelView,
+    private val keyguardStatusBarViewComponentFactory: KeyguardStatusBarViewComponent.Factory,
+) : KeyguardSection() {
+
+    private val statusBarViewId = R.id.keyguard_header
+
+    override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+            return
+        }
+
+        notificationPanelView.findViewById<View>(statusBarViewId)?.let {
+            (it.parent as ViewGroup).removeView(it)
+        }
+
+        val view =
+            LayoutInflater.from(constraintLayout.context)
+                .inflate(R.layout.keyguard_status_bar, constraintLayout, false)
+                as KeyguardStatusBarView
+
+        constraintLayout.addView(view)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+            return
+        }
+
+        val statusBarView =
+            constraintLayout.findViewById<KeyguardStatusBarView>(statusBarViewId) ?: return
+
+        val provider =
+            object : ShadeViewStateProvider {
+                override val lockscreenShadeDragProgress: Float = 0f
+                override val panelViewExpandedHeight: Float = 0f
+                override fun shouldHeadsUpBeVisible(): Boolean {
+                    return false
+                }
+            }
+        val statusBarViewComponent =
+            keyguardStatusBarViewComponentFactory.build(statusBarView, provider)
+        val controller = statusBarViewComponent.keyguardStatusBarViewController
+        controller.init()
+    }
+
+    override fun applyConstraints(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            constrainHeight(statusBarViewId, Utils.getStatusBarHeaderHeightKeyguard(context))
+            connect(statusBarViewId, TOP, PARENT_ID, TOP)
+            connect(statusBarViewId, START, PARENT_ID, START)
+            connect(statusBarViewId, END, PARENT_ID, END)
+        }
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(statusBarViewId)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index c7b0cb9..1cb10bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -91,7 +91,7 @@
                     it.requireViewById<ViewGroup>(R.id.status_view_media_container)
                 )
                 keyguardViewConfigurator.get().keyguardStatusViewController = controller
-                notificationPanelViewController.get().updateStatusBarViewController()
+                notificationPanelViewController.get().updateStatusViewController()
             }
         }
     }
@@ -100,6 +100,8 @@
         constraintSet.apply {
             constrainWidth(statusViewId, MATCH_CONSTRAINT)
             constrainHeight(statusViewId, WRAP_CONTENT)
+            // TODO(b/296122465): Constrain to the top of [DefaultStatusBarSection] and remove the
+            // extra margin below.
             connect(statusViewId, TOP, PARENT_ID, TOP)
             connect(statusViewId, START, PARENT_ID, START)
             connect(statusViewId, END, PARENT_ID, END)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 05c9323..cfcbdac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -18,26 +18,39 @@
 
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the lockscreen scene. */
 @SysUISingleton
 class LockscreenSceneViewModel
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     authenticationInteractor: AuthenticationInteractor,
     val longPress: KeyguardLongPressViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: Flow<SceneKey> =
-        authenticationInteractor.isUnlocked.map { isUnlocked ->
-            if (isUnlocked) {
-                SceneKey.Gone
-            } else {
-                SceneKey.Bouncer
-            }
+    val upDestinationSceneKey: StateFlow<SceneKey> =
+        authenticationInteractor.isUnlocked
+            .map { isUnlocked -> upDestinationSceneKey(isUnlocked) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = upDestinationSceneKey(authenticationInteractor.isUnlocked.value),
+            )
+
+    private fun upDestinationSceneKey(isUnlocked: Boolean): SceneKey {
+        return if (isUnlocked) {
+            SceneKey.Gone
+        } else {
+            SceneKey.Bouncer
         }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 66067b1..8143f99 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -29,36 +29,18 @@
 constructor(
     @FaceAuthLog private val logBuffer: LogBuffer,
 ) {
-    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
+    fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) {
         logBuffer.log(
             TAG,
             DEBUG,
             {
-                str1 = uiEvent.reason
+                str1 = "${uiEvent?.reason}"
                 str2 = ignoredReason
             },
             { "Ignoring trigger because $str2, Trigger reason: $str1" }
         )
     }
 
-    fun queuingRequestWhileCancelling(
-        alreadyQueuedRequest: FaceAuthUiEvent?,
-        newRequest: FaceAuthUiEvent
-    ) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            {
-                str1 = alreadyQueuedRequest?.reason
-                str2 = newRequest.reason
-            },
-            {
-                "Face auth requested while previous request is being cancelled, " +
-                    "already queued request: $str1 queueing the new request: $str2"
-            }
-        )
-    }
-
     fun authenticating(uiEvent: FaceAuthUiEvent) {
         logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" })
     }
@@ -161,15 +143,6 @@
         )
     }
 
-    fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { str1 = "${faceAuthRequestedWhileCancellation?.reason}" },
-            { "Received cancellation error and starting queued face auth request: $str1" }
-        )
-    }
-
     fun faceAuthSuccess(result: FaceManager.AuthenticationResult) {
         logBuffer.log(
             TAG,
@@ -182,31 +155,10 @@
         )
     }
 
-    fun observedConditionChanged(newValue: Boolean, context: String) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            {
-                bool1 = newValue
-                str1 = context
-            },
-            { "Observed condition changed: $str1, new value: $bool1" }
-        )
-    }
-
     fun canFaceAuthRunChanged(canRun: Boolean) {
         logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" })
     }
 
-    fun canRunDetectionChanged(canRunDetection: Boolean) {
-        logBuffer.log(
-            TAG,
-            DEBUG,
-            { bool1 = canRunDetection },
-            { "canRunDetection value changed to $bool1" }
-        )
-    }
-
     fun cancellingFaceAuth() {
         logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
     }
@@ -236,7 +188,7 @@
         logBuffer.log(
             TAG,
             DEBUG,
-            { str1 = "$uiEvent" },
+            { str1 = uiEvent.reason },
             { "Requesting face auth for trigger: $str1" }
         )
     }
@@ -269,4 +221,77 @@
     fun faceLockedOut(@CompileTimeConstant reason: String) {
         logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason")
     }
+
+    fun queueingRequest(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = "$uiEvent"
+                bool1 = fallbackToDetection
+            },
+            { "Queueing $str1 request for face auth, fallbackToDetection: $bool1" }
+        )
+    }
+
+    fun notProcessingRequestYet(
+        uiEvent: FaceAuthUiEvent?,
+        canRunAuth: Boolean,
+        canRunDetect: Boolean,
+        cancelInProgress: Boolean
+    ) {
+        uiEvent?.let {
+            logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    str1 = uiEvent.reason
+                    bool1 = canRunAuth
+                    bool2 = canRunDetect
+                    bool3 = cancelInProgress
+                },
+                {
+                    "Waiting to process request: reason: $str1, " +
+                        "canRunAuth: $bool1, " +
+                        "canRunDetect: $bool2, " +
+                        "cancelInProgress: $bool3"
+                }
+            )
+        }
+    }
+
+    fun processingRequest(uiEvent: FaceAuthUiEvent?, fallbackToDetection: Boolean) {
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = "${uiEvent?.reason}"
+                bool1 = fallbackToDetection
+            },
+            { "Processing face auth request: $str1, fallbackToDetect: $bool1" }
+        )
+    }
+
+    fun clearingPendingAuthRequest(
+        @CompileTimeConstant loggingContext: String,
+        uiEvent: FaceAuthUiEvent?,
+        fallbackToDetection: Boolean?
+    ) {
+        uiEvent?.let {
+            logBuffer.log(
+                TAG,
+                DEBUG,
+                {
+                    str1 = uiEvent.reason
+                    str2 = "$fallbackToDetection"
+                    str3 = loggingContext
+                },
+                {
+                    "Clearing pending auth: $str1, " +
+                        "fallbackToDetection: $str2, " +
+                        "reason: $str3"
+                }
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 60fd104..cf64a83 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -274,7 +274,9 @@
             recentsViewController.hasRecentTasks
         }
 
-    override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+    override fun shouldShowStickyContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+    override fun shouldShowServiceTargets() = false
 
     private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 5d732fb..cbb7e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.mediaprojection.appselector.view
 
 import android.app.ActivityOptions
+import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
 import android.app.IActivityTaskManager
 import android.graphics.Rect
 import android.os.Binder
@@ -129,6 +130,9 @@
                 view.width,
                 view.height
             )
+        activityOptions.setPendingIntentBackgroundActivityStartMode(
+            MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+        )
         activityOptions.launchCookie = launchCookie
 
         activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index 9b9d561..0c77054 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -28,7 +28,6 @@
 import android.view.WindowManager
 import androidx.core.content.getSystemService
 import androidx.core.content.res.use
-import com.android.internal.R as AndroidR
 import com.android.systemui.R
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -147,25 +146,14 @@
         previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
 
         val currentRotation: Int = display.rotation
-        val displayWidthPx = windowMetrics.bounds.width()
-        val displayHeightPx = windowMetrics.bounds.height()
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
         val isLargeScreen = isLargeScreen(context)
-        val taskbarSize =
-            if (isLargeScreen) {
-                resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
-            } else {
-                0
-            }
 
         previewPositionHelper.updateThumbnailMatrix(
             previewRect,
             thumbnailData,
             measuredWidth,
             measuredHeight,
-            displayWidthPx,
-            displayHeightPx,
-            taskbarSize,
             isLargeScreen,
             currentRotation,
             isRtl
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 146b5f5..79e7b71 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -359,6 +359,7 @@
 
     public void setBackgroundExecutor(Executor bgExecutor) {
         mBgExecutor = bgExecutor;
+        mRotationButtonController.setBgExecutor(bgExecutor);
     }
 
     public void setDisplayTracker(DisplayTracker displayTracker) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 0842fe0..ea8eb36 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -321,7 +321,7 @@
         // When switched to a secondary user, the sysUI is still running in the main user, we will
         // need to update the shortcut in the secondary user.
         if (user == getCurrentRunningUser()) {
-            updateNoteTaskAsUserInternal(user)
+            launchUpdateNoteTaskAsUser(user)
         } else {
             // TODO(b/278729185): Replace fire and forget service with a bounded service.
             val intent = NoteTaskControllerUpdateService.createIntent(context)
@@ -330,23 +330,25 @@
     }
 
     @InternalNoteTaskApi
-    fun updateNoteTaskAsUserInternal(user: UserHandle) {
-        if (!userManager.isUserUnlocked(user)) {
-            debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
-            return
-        }
+    fun launchUpdateNoteTaskAsUser(user: UserHandle) {
+        applicationScope.launch {
+            if (!userManager.isUserUnlocked(user)) {
+                debugLog { "updateNoteTaskAsUserInternal call but user locked: user=$user" }
+                return@launch
+            }
 
-        val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
-        val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
+            val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user)
+            val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty()
 
-        setNoteTaskShortcutEnabled(hasNotesRoleHolder, user)
+            setNoteTaskShortcutEnabled(hasNotesRoleHolder, user)
 
-        if (hasNotesRoleHolder) {
-            shortcutManager.enableShortcuts(listOf(SHORTCUT_ID))
-            val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user)
-            shortcutManager.updateShortcuts(listOf(updatedShortcut))
-        } else {
-            shortcutManager.disableShortcuts(listOf(SHORTCUT_ID))
+            if (hasNotesRoleHolder) {
+                shortcutManager.enableShortcuts(listOf(SHORTCUT_ID))
+                val updatedShortcut = roleManager.createNoteShortcutInfoAsUser(context, user)
+                shortcutManager.updateShortcuts(listOf(updatedShortcut))
+            } else {
+                shortcutManager.disableShortcuts(listOf(SHORTCUT_ID))
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
index 3e352af..486fde1 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskControllerUpdateService.kt
@@ -44,7 +44,7 @@
     override fun onCreate() {
         super.onCreate()
         // TODO(b/278729185): Replace fire and forget service with a bounded service.
-        controller.updateNoteTaskAsUserInternal(user)
+        controller.launchUpdateNoteTaskAsUser(user)
         stopSelf()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index fe1034a..3f609a2 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -65,12 +65,6 @@
      * [NoteTaskController], ensure custom actions can be triggered (i.e., keyboard shortcut).
      */
     private fun initializeHandleSystemKey() {
-        val callbacks =
-            object : CommandQueue.Callbacks {
-                override fun handleSystemKey(key: KeyEvent) {
-                    key.toNoteTaskEntryPointOrNull()?.let(controller::showNoteTask)
-                }
-            }
         commandQueue.addCallback(callbacks)
     }
 
@@ -142,7 +136,7 @@
  */
 private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? =
     when {
-        keyCode == KEYCODE_STYLUS_BUTTON_TAIL -> TAIL_BUTTON
+        keyCode == KEYCODE_STYLUS_BUTTON_TAIL && action == KeyEvent.ACTION_UP -> TAIL_BUTTON
         keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT
         else -> null
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 544e6ad..e382eca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -20,6 +20,7 @@
 import static android.provider.Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT;
 
 import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
+import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards;
 
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -210,7 +211,7 @@
         public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
             Log.i(TAG, "Successfully retrieved wallet cards.");
             mIsWalletUpdating = false;
-            List<WalletCard> cards = response.getWalletCards();
+            List<WalletCard> cards = getPaymentCards(response.getWalletCards());
             if (cards.isEmpty()) {
                 Log.d(TAG, "No wallet cards exist.");
                 mCardViewDrawable = null;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
new file mode 100644
index 0000000..e9f907c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.qs.tiles.base.actions
+
+import android.content.Intent
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/**
+ * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
+ * dismissing and tile from-view animations.
+ */
+@SysUISingleton
+class QSTileIntentUserActionHandler
+@Inject
+constructor(private val activityStarter: ActivityStarter) {
+
+    fun handle(userAction: QSTileUserAction, intent: Intent) {
+        val animationController: ActivityLaunchAnimator.Controller? =
+            userAction.view?.let {
+                ActivityLaunchAnimator.Controller.fromView(
+                    it,
+                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+                )
+            }
+        activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
new file mode 100644
index 0000000..d6c9705
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import androidx.annotation.WorkerThread
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+
+interface QSTileDataToStateMapper<DATA_TYPE> {
+
+    /**
+     * Maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer. It's called
+     * on a background thread, so it's safe to perform long running operations there.
+     */
+    @WorkerThread fun map(config: QSTileConfig, data: DATA_TYPE): QSTileState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
new file mode 100644
index 0000000..c2a75fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -0,0 +1,185 @@
+package com.android.systemui.qs.tiles.base.viewmodel
+
+import androidx.annotation.CallSuper
+import androidx.annotation.VisibleForTesting
+import com.android.internal.util.Preconditions
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides a hassle-free way to implement new tiles according to current System UI architecture
+ * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
+ * [QSTileLifecycle.ALIVE] state.
+ */
+abstract class BaseQSTileViewModel<DATA_TYPE>
+@VisibleForTesting
+constructor(
+    final override val config: QSTileConfig,
+    private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+    private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+    private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
+    private val backgroundDispatcher: CoroutineDispatcher,
+    private val tileScope: CoroutineScope,
+) : QSTileViewModel {
+
+    /**
+     * @param config contains all the static information (like TileSpec) about the tile.
+     * @param userActionInteractor encapsulates user input processing logic. Use it to start
+     *   activities, show dialogs or otherwise update the tile state.
+     * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+     * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call
+     *   interactors methods. This should likely to be @Background CoroutineDispatcher.
+     * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer.
+     *   It's called in [backgroundDispatcher], so it's safe to perform long running operations
+     *   there.
+     */
+    constructor(
+        config: QSTileConfig,
+        userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+        tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+        mapper: QSTileDataToStateMapper<DATA_TYPE>,
+        backgroundDispatcher: CoroutineDispatcher,
+    ) : this(
+        config,
+        userActionInteractor,
+        tileDataInteractor,
+        mapper,
+        backgroundDispatcher,
+        CoroutineScope(SupervisorJob())
+    )
+
+    private val userInputs: MutableSharedFlow<QSTileUserAction> =
+        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    private val userIds: MutableSharedFlow<Int> =
+        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+    private val forceUpdates: MutableSharedFlow<Unit> =
+        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    private lateinit var tileData: SharedFlow<DATA_TYPE>
+
+    override lateinit var state: SharedFlow<QSTileState>
+    override val isAvailable: StateFlow<Boolean> =
+        tileDataInteractor
+            .availability()
+            .flowOn(backgroundDispatcher)
+            .stateIn(
+                tileScope,
+                SharingStarted.WhileSubscribed(),
+                false,
+            )
+
+    private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
+
+    @CallSuper
+    override fun forceUpdate() {
+        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+        forceUpdates.tryEmit(Unit)
+    }
+
+    @CallSuper
+    override fun onUserIdChanged(userId: Int) {
+        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+        userIds.tryEmit(userId)
+    }
+
+    @CallSuper
+    override fun onActionPerformed(userAction: QSTileUserAction) {
+        Preconditions.checkState(tileData.replayCache.isNotEmpty())
+        Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+        userInputs.tryEmit(userAction)
+    }
+
+    @CallSuper
+    override fun onLifecycle(lifecycle: QSTileLifecycle) {
+        when (lifecycle) {
+            QSTileLifecycle.ALIVE -> {
+                Preconditions.checkState(currentLifeState == QSTileLifecycle.DEAD)
+                tileData = createTileDataFlow()
+                state =
+                    tileData
+                        // TODO(b/299908705): log data and corresponding tile state
+                        .map { mapper.map(config, it) }
+                        .flowOn(backgroundDispatcher)
+                        .shareIn(
+                            tileScope,
+                            SharingStarted.WhileSubscribed(),
+                            replay = 1,
+                        )
+            }
+            QSTileLifecycle.DEAD -> {
+                Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+                tileScope.coroutineContext.cancelChildren()
+            }
+        }
+        currentLifeState = lifecycle
+    }
+
+    private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+        userIds
+            .flatMapLatest { userId ->
+                merge(
+                        userInputFlow(),
+                        forceUpdates.map { StateUpdateTrigger.ForceUpdate },
+                    )
+                    .onStart { emit(StateUpdateTrigger.InitialRequest) }
+                    .map { trigger -> QSTileDataRequest(userId, trigger) }
+            }
+            .flatMapLatest { request ->
+                // 1) get an updated data source
+                // 2) process user input, possibly triggering new data to be emitted
+                // This handles the case when the data isn't buffered in the interactor
+                // TODO(b/299908705): Log events that trigger data flow to update
+                val dataFlow = tileDataInteractor.tileData(request)
+                if (request.trigger is StateUpdateTrigger.UserAction<*>) {
+                    userActionInteractor.handleInput(
+                        request.trigger.action,
+                        request.trigger.tileData as DATA_TYPE,
+                    )
+                }
+                dataFlow
+            }
+            .flowOn(backgroundDispatcher)
+            .shareIn(
+                tileScope,
+                SharingStarted.WhileSubscribed(),
+                replay = 1, // we only care about the most recent value
+            )
+
+    private fun userInputFlow(): Flow<StateUpdateTrigger> {
+        data class StateWithData<T>(val state: QSTileState, val data: T)
+
+        // Skip the input until there is some data
+        return userInputs.sample(
+            state.combine(tileData) { state, data -> StateWithData(state, data) }
+        ) { input, stateWithData ->
+            StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
index 39db703..1d5c1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 enum class QSTileLifecycle {
-    ON_CREATE,
-    ON_DESTROY,
+    ALIVE,
+    DEAD,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 49077f3..d66d0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -1,28 +1,35 @@
 package com.android.systemui.qs.tiles.viewmodel
 
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
 
 /**
  * Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers.
+ * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
+ * to pass compliance tests.
+ *
+ * All methods of this view model should be considered running on the main thread. This means no
+ * synchronous long running operations are permitted in any method.
  */
 interface QSTileViewModel {
 
     /**
-     * State of the tile to be shown by the view. Favor reactive consumption over the
-     * [StateFlow.value], because there is no guarantee that current value would be available at any
-     * time.
+     * State of the tile to be shown by the view. It's guaranteed that it's only accessed between
+     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD].
      */
-    val state: StateFlow<QSTileState>
+    val state: SharedFlow<QSTileState>
 
     val config: QSTileConfig
 
-    val isAvailable: Flow<Boolean>
+    /**
+     * Specifies whether this device currently supports this tile. This might be called outside of
+     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds (for example in Edit Mode).
+     */
+    val isAvailable: StateFlow<Boolean>
 
     /**
      * Handles ViewModel lifecycle. Implementations should be inactive outside of
-     * [QSTileLifecycle.ON_CREATE] and [QSTileLifecycle.ON_DESTROY] bounds.
+     * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds.
      */
     fun onLifecycle(lifecycle: QSTileLifecycle)
 
@@ -33,9 +40,17 @@
      */
     fun onUserIdChanged(userId: Int)
 
-    /** Triggers emit of the new [QSTileState] in [state]. */
+    /** Triggers the emission of the new [QSTileState] in a [state]. */
     fun forceUpdate()
 
     /** Notifies underlying logic about user input. */
     fun onActionPerformed(userAction: QSTileUserAction)
 }
+
+/**
+ * Returns the immediate state of the tile or null if the state haven't been collected yet. Favor
+ * reactive consumption over the [currentState], because there is no guarantee that current value
+ * would be available at any time.
+ */
+val QSTileViewModel.currentState: QSTileState?
+    get() = state.replayCache.lastOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d23beda..cef52e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -151,6 +151,7 @@
     private SysUiState mSysUiState;
     private final Handler mHandler;
     private final Lazy<NavigationBarController> mNavBarControllerLazy;
+    private final ScreenPinningRequest mScreenPinningRequest;
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Provider<SceneInteractor> mSceneInteractor;
 
@@ -185,9 +186,7 @@
         @Override
         public void startScreenPinning(int taskId) {
             verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () ->
-                    mCentralSurfacesOptionalLazy.get().ifPresent(
-                            statusBar -> statusBar.showScreenPinningRequest(taskId,
-                                    false /* allowCancel */)));
+                    mScreenPinningRequest.showPrompt(taskId, false /* allowCancel */));
         }
 
         @Override
@@ -572,6 +571,7 @@
             Lazy<NavigationBarController> navBarControllerLazy,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Lazy<ShadeViewController> shadeViewControllerLazy,
+            ScreenPinningRequest screenPinningRequest,
             NavigationModeController navModeController,
             NotificationShadeWindowController statusBarWinController,
             SysUiState sysUiState,
@@ -601,6 +601,7 @@
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mHandler = new Handler();
         mNavBarControllerLazy = navBarControllerLazy;
+        mScreenPinningRequest = screenPinningRequest;
         mStatusBarWinController = statusBarWinController;
         mSceneInteractor = sceneInteractor;
         mUserTracker = userTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 346acf9..77fcd68 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -53,8 +53,10 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.CoreStartable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.UserTracker;
@@ -69,8 +71,9 @@
 
 import dagger.Lazy;
 
+@SysUISingleton
 public class ScreenPinningRequest implements View.OnClickListener,
-        NavigationModeController.ModeChangedListener {
+        NavigationModeController.ModeChangedListener, CoreStartable {
     private static final String TAG = "ScreenPinningRequest";
 
     private final Context mContext;
@@ -113,6 +116,9 @@
         mUserTracker = userTracker;
     }
 
+    @Override
+    public void start() {}
+
     public void clearPrompt() {
         if (mRequestWindow != null) {
             mWindowManager.removeView(mRequestWindow);
@@ -144,7 +150,8 @@
         mNavBarMode = mode;
     }
 
-    public void onConfigurationChanged() {
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
         if (mRequestWindow != null) {
             mRequestWindow.onConfigurationChanged();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 31597c1..4bc93a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.scene.shared.model
 
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
 /**
  * Defines interface for classes that can describe a "scene".
@@ -34,31 +32,26 @@
     val key: SceneKey
 
     /**
-     * Returns a mapping between [UserAction] and flows that emit a [SceneModel].
+     * The mapping between [UserAction] and destination [SceneModel]s.
      *
-     * When the scene framework detects the user action, it starts a transition to the scene
-     * described by the latest value in the flow that's mapped from that user action.
+     * When the scene framework detects a user action, if the current scene has a map entry for that
+     * user action, the framework starts a transition to the scene in the map.
      *
-     * Once the [Scene] becomes the current one, the scene framework will invoke this method and set
-     * up collectors to watch for new values emitted to each of the flows. If a value is added to
-     * the map at a given [UserAction], the framework will set up user input handling for that
-     * [UserAction] and, if such a user action is detected, the framework will initiate a transition
-     * to that [SceneModel].
+     * Once the [Scene] becomes the current one, the scene framework will read this property and set
+     * up a collector to watch for new mapping values. If every map entry provided by the scene, the
+     * framework will set up user input handling for its [UserAction] and, if such a user action is
+     * detected, initiate a transition to the specified [SceneModel].
      *
-     * Note that calling this method does _not_ mean that the given user action has occurred.
-     * Instead, the method is called before any user action/gesture is detected so that the
-     * framework can decide whether to set up gesture/input detectors/listeners for that type of
-     * user action.
+     * Note that reading from this method does _not_ mean that any user action has occurred.
+     * Instead, the property is read before any user action/gesture is detected so that the
+     * framework can decide whether to set up gesture/input detectors/listeners in case user actions
+     * of the given types ever occur.
      *
      * Note that a missing value for a specific [UserAction] means that the user action of the given
      * type is not currently active in the scene and should be ignored by the framework, while the
      * current scene is this one.
-     *
-     * The API is designed such that it's possible to emit ever-changing values for each
-     * [UserAction] to enable, disable, or change the destination scene of a given user action.
      */
-    fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
-        MutableStateFlow(emptyMap<UserAction, SceneModel>()).asStateFlow()
+    val destinationScenes: StateFlow<Map<UserAction, SceneModel>>
 }
 
 /** Enumerates all scene framework supported user actions. */
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index c5bc2fb..03c3f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -150,16 +150,18 @@
     }
 
     /**
-     * Returns a [RequestCallback] that calls [RequestCallback.onFinish] only when all callbacks for
-     * id created have finished.
+     * Returns a [RequestCallback] that wraps [originalCallback].
      *
-     * If any callback created calls [reportError], then following [onFinish] are not considered.
+     * Each [RequestCallback] created with [createCallbackForId] is expected to be used with either
+     * [reportError] or [onFinish]. Once they are both called:
+     * - If any finished with an error, [reportError] of [originalCallback] is called
+     * - Otherwise, [onFinish] is called.
      */
     private class MultiResultCallbackWrapper(
         private val originalCallback: RequestCallback,
     ) {
         private val idsPending = mutableSetOf<Int>()
-        private var errorReported = false
+        private val idsWithErrors = mutableSetOf<Int>()
 
         /**
          * Creates a callback for [id].
@@ -172,23 +174,32 @@
             idsPending += id
             return object : RequestCallback {
                 override fun reportError() {
-                    Log.d(TAG, "ReportError id=$id")
-                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
-                    Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "reportError id=$id")
-                    originalCallback.reportError()
-                    errorReported = true
+                    endTrace("reportError id=$id")
+                    idsWithErrors += id
+                    idsPending -= id
+                    reportToOriginalIfNeeded()
                 }
 
                 override fun onFinish() {
-                    Log.d(TAG, "onFinish id=$id")
-                    if (errorReported) return
+                    endTrace("onFinish id=$id")
                     idsPending -= id
-                    Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "onFinish id=$id")
-                    if (idsPending.isEmpty()) {
-                        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
-                        originalCallback.onFinish()
-                    }
+                    reportToOriginalIfNeeded()
                 }
+
+                private fun endTrace(reason: String) {
+                    Log.d(TAG, "Finished waiting for id=$id. $reason")
+                    Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
+                    Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, reason)
+                }
+            }
+        }
+
+        private fun reportToOriginalIfNeeded() {
+            if (idsPending.isNotEmpty()) return
+            if (idsWithErrors.isEmpty()) {
+                originalCallback.onFinish()
+            } else {
+                originalCallback.reportError()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 1e8542f..32df5121b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -99,7 +99,11 @@
 
     /** Informs about coarse grained state of the Controller. */
     public interface RequestCallback {
-        /** Respond to the current request indicating the screenshot request failed. */
+        /**
+         * Respond to the current request indicating the screenshot request failed.
+         * <p>
+         * After this, the service will be disconnected and all visible UI is removed.
+         */
         void reportError();
 
         /** The controller has completed handling this request UI has been removed */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3aa7bac..d2e80fc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -205,7 +205,6 @@
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
@@ -235,7 +234,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.Optional;
 import java.util.function.Consumer;
 
@@ -367,7 +365,6 @@
     private float mExpandedHeight = 0;
     /** The current squish amount for the predictive back animation */
     private float mCurrentBackProgress = 0.0f;
-    private boolean mTracking;
     @Deprecated
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
@@ -383,7 +380,6 @@
     private int mMaxAllowedKeyguardNotifications;
     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
-    private KeyguardStatusBarView mKeyguardStatusBar;
     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
     private KeyguardStatusViewController mKeyguardStatusViewController;
     private final LockIconViewController mLockIconViewController;
@@ -1001,7 +997,7 @@
         // cause blurring. This will eventually be re-enabled by the panel view on
         // ACTION_UP, since the user's finger might still be down after a swipe to
         // unlock gesture, and we don't want that to cause blurring either.
-        mDepthController.setBlursDisabledForUnlock(mTracking);
+        mDepthController.setBlursDisabledForUnlock(isTracking());
 
         if (playingCannedAnimation && !isWakeAndUnlockNotFromDream) {
             // Hide the panel so it's not in the way or the surface behind the
@@ -1037,7 +1033,6 @@
     @VisibleForTesting
     void onFinishInflate() {
         loadDimens();
-        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
 
         FrameLayout userAvatarContainer = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -1055,7 +1050,7 @@
 
         mKeyguardStatusBarViewController =
                 mKeyguardStatusBarViewComponentFactory.build(
-                                mKeyguardStatusBar,
+                                mView.findViewById(R.id.keyguard_header),
                                 mShadeViewStateProvider)
                         .getKeyguardStatusBarViewController();
         mKeyguardStatusBarViewController.init();
@@ -1228,7 +1223,7 @@
     private void updateViewControllers(
             FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
-        updateStatusBarViewController();
+        updateStatusViewController();
         if (mKeyguardUserSwitcherController != null) {
             // Try to close the switcher so that callbacks are triggered if necessary.
             // Otherwise, NPV can get into a state where some of the views are still hidden
@@ -1259,7 +1254,7 @@
     }
 
     /** Updates the StatusBarViewController and updates any that depend on it. */
-    public void updateStatusBarViewController() {
+    public void updateStatusViewController() {
         // Re-associate the KeyguardStatusViewController
         if (mKeyguardStatusViewController != null) {
             mKeyguardStatusViewController.onDestroy();
@@ -1844,6 +1839,9 @@
     /** Returns extra space available to show the shelf on lockscreen */
     @VisibleForTesting
     float getVerticalSpaceForLockscreenShelf() {
+        if (mSplitShadeEnabled) {
+            return 0f;
+        }
         final float lockIconPadding = getLockIconPadding();
 
         final float noShelfOverlapBottomPadding =
@@ -2544,10 +2542,10 @@
     private void onHeightUpdated(float expandedHeight) {
         if (expandedHeight <= 0) {
             mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
-                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                    mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx);
         } else if (isFullyExpanded()) {
             mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
-                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                    mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx);
         }
         if (!mQsController.getExpanded() || mQsController.isExpandImmediate()
                 || mIsExpandingOrCollapsing && mQsController.getExpandedWhenExpandingStarted()) {
@@ -2740,7 +2738,7 @@
             mAnimateAfterExpanding = animate;
             mUpdateFlingOnLayout = false;
             abortAnimations();
-            if (mTracking) {
+            if (isTracking()) {
                 // The panel is expanded after this call.
                 onTrackingStopped(true /* expands */);
             }
@@ -2827,7 +2825,7 @@
 
     private void onTrackingStarted() {
         endClosing();
-        mTracking = true;
+        mShadeRepository.setLegacyShadeTracking(true);
         mTrackingStartedListener.onTrackingStarted();
         notifyExpandingStarted();
         updateExpansionAndVisibility();
@@ -2841,7 +2839,7 @@
     }
 
     private void onTrackingStopped(boolean expand) {
-        mTracking = false;
+        mShadeRepository.setLegacyShadeTracking(false);
 
         updateExpansionAndVisibility();
         if (expand) {
@@ -3013,7 +3011,7 @@
     }
 
     private void updateExpandedHeight(float expandedHeight) {
-        if (mTracking) {
+        if (isTracking()) {
             mNotificationStackScrollLayoutController
                     .setExpandingVelocity(getCurrentExpandVelocity());
         }
@@ -3102,7 +3100,7 @@
         mTouchDisabled = disabled;
         if (mTouchDisabled) {
             cancelHeightAnimator();
-            if (mTracking) {
+            if (isTracking()) {
                 onTrackingStopped(true /* expanded */);
             }
             notifyExpandingFinished();
@@ -3341,7 +3339,7 @@
 
     @Override
     public void blockExpansionForCurrentTouch() {
-        mBlockingExpansionForCurrentTouch = mTracking;
+        mBlockingExpansionForCurrentTouch = isTracking();
     }
 
     @Override
@@ -3355,7 +3353,7 @@
         ipw.print("mIsLaunchAnimationRunning="); ipw.println(mIsLaunchAnimationRunning);
         ipw.print("mOverExpansion="); ipw.println(mOverExpansion);
         ipw.print("mExpandedHeight="); ipw.println(mExpandedHeight);
-        ipw.print("mTracking="); ipw.println(mTracking);
+        ipw.print("isTracking()="); ipw.println(isTracking());
         ipw.print("mExpanding="); ipw.println(mExpanding);
         ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
         ipw.print("mKeyguardNotificationBottomPadding=");
@@ -3693,7 +3691,7 @@
             mQsController.beginJankMonitoring(isFullyCollapsed());
         }
         mInitialOffsetOnTouch = expandedHeight;
-        if (!mTracking || isFullyCollapsed()) {
+        if (!isTracking() || isFullyCollapsed()) {
             mInitialExpandY = newY;
             mInitialExpandX = newX;
         } else {
@@ -3711,7 +3709,7 @@
         mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false);
         mTrackingPointer = -1;
         mAmbientState.setSwipingUp(false);
-        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
+        if ((isTracking() && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop
                 || Math.abs(y - mInitialExpandY) > mTouchSlop
                 || (!isFullyExpanded() && !isFullyCollapsed())
                 || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
@@ -3732,8 +3730,6 @@
                     expand = true;
                     mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard",
                             forceCancel, expand);
-                } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
-                    expand = false;
                 } else {
                     // If we get a cancel, put the shade back to the state it was in when the
                     // gesture started
@@ -3875,7 +3871,7 @@
             return;
         }
 
-        if (mTracking && !(mBlockingExpansionForCurrentTouch
+        if (isTracking() && !(mBlockingExpansionForCurrentTouch
                 || mQsController.isTrackingBlocked())) {
             return;
         }
@@ -3901,7 +3897,7 @@
             float maxPanelHeight = getMaxPanelTransitionDistance();
             if (mHeightAnimator == null) {
                 // Split shade has its own overscroll logic
-                if (mTracking) {
+                if (isTracking()) {
                     float overExpansionPixels = Math.max(0, h - maxPanelHeight);
                     setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
                 }
@@ -3988,12 +3984,12 @@
     }
 
     public boolean isTracking() {
-        return mTracking;
+        return mShadeRepository.getLegacyShadeTracking().getValue();
     }
 
     @Override
     public boolean canBeCollapsed() {
-        return !isFullyCollapsed() && !mTracking && !mClosing;
+        return !isFullyCollapsed() && !isTracking() && !mClosing;
     }
 
     @Override
@@ -4085,7 +4081,7 @@
     @Override
     public void updateExpansionAndVisibility() {
         mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+                mExpandedFraction, isExpanded(), isTracking(), mExpansionDragDownAmountPx);
 
         updateVisibility();
     }
@@ -4095,7 +4091,7 @@
         return mExpandedFraction > 0f
                 || mInstantExpanding
                 || isPanelVisibleBecauseOfHeadsUp()
-                || mTracking
+                || isTracking()
                 || mHeightAnimator != null
                 || isPanelVisibleBecauseScrimIsAnimatingOff()
                 && !mIsSpringBackAnimation;
@@ -4798,7 +4794,7 @@
                                 + " mAnimatingOnDown: true, mClosing: true");
                         return true;
                     }
-                    if (!mTracking || isFullyCollapsed()) {
+                    if (!isTracking() || isFullyCollapsed()) {
                         mInitialExpandY = y;
                         mInitialExpandX = x;
                     } else {
@@ -4893,10 +4889,9 @@
                 return false;
             }
 
-            // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+            // Do not allow panel expansion if bouncer is scrimmed,
             // otherwise user would be able to pull down QS or expand the shade.
-            if (mCentralSurfaces.isBouncerShowingScrimmed()
-                    || mCentralSurfaces.isBouncerShowingOverDream()) {
+            if (mCentralSurfaces.isBouncerShowingScrimmed()) {
                 mShadeLog.logMotionEvent(event,
                         "onTouch: ignore touch, bouncer scrimmed or showing over dream");
                 return false;
@@ -4975,7 +4970,7 @@
 
             // If dragging should not expand the notifications shade, then return false.
             if (!mNotificationsDragEnabled) {
-                if (mTracking) {
+                if (isTracking()) {
                     // Turn off tracking if it's on or the shade can get stuck in the down position.
                     onTrackingStopped(true /* expand */);
                 }
@@ -5101,7 +5096,9 @@
                             && (Math.abs(h) > Math.abs(x - mInitialExpandX)
                             || mIgnoreXTouchSlop)) {
                         mTouchSlopExceeded = true;
-                        if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+                        if (mGestureWaitForTouchSlop
+                                && !isTracking()
+                                && !mCollapsedAndHeadsUpOnDown) {
                             if (mInitialOffsetOnTouch != 0f) {
                                 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                                 h = 0;
@@ -5116,7 +5113,7 @@
                         mTouchAboveFalsingThreshold = true;
                         mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
                     }
-                    if ((!mGestureWaitForTouchSlop || mTracking)
+                    if ((!mGestureWaitForTouchSlop || isTracking())
                             && !(mBlockingExpansionForCurrentTouch
                             || mQsController.isTrackingBlocked())) {
                         // Count h==0 as part of swipe-up,
@@ -5142,7 +5139,7 @@
                     }
                     break;
             }
-            return !mGestureWaitForTouchSlop || mTracking;
+            return !mGestureWaitForTouchSlop || isTracking();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 9a16538..ff0d78f8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -201,8 +201,6 @@
     private float mInitialTouchY;
     /** whether current touch Y delta is above falsing threshold */
     private boolean mTouchAboveFalsingThreshold;
-    /** whether we are tracking a touch on QS container */
-    private boolean mTracking;
     /** pointerId of the pointer we're currently tracking */
     private int mTrackingPointer;
 
@@ -600,7 +598,7 @@
 
     @VisibleForTesting
     boolean isTracking() {
-        return mTracking;
+        return mShadeRepository.getLegacyQsTracking().getValue();
     }
 
     public boolean getFullyExpanded() {
@@ -613,10 +611,14 @@
         // split shade as there QS are always expanded so every collapsing motion is motion from
         // expanded QS to closed panel
         return mExpandImmediate || (mExpanded
-                && !mTracking && !isExpansionAnimating()
+                && !isTracking() && !isExpansionAnimating()
                 && !mExpansionFromOverscroll);
     }
 
+    private void setTracking(boolean tracking) {
+        mShadeRepository.setLegacyQsTracking(tracking);
+    }
+
     private boolean isQsFragmentCreated() {
         return mQs != null;
     }
@@ -1601,7 +1603,7 @@
         if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
             // Down in the empty area while fully expanded - go to QS.
             mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
-            mTracking = true;
+            setTracking(true);
             traceQsJank(true, false);
             mConflictingExpansionGesture = true;
             onExpansionStarted();
@@ -1616,9 +1618,9 @@
         // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
         if (!mSplitShadeEnabled && !mLastShadeFlingWasExpanding
                 && computeExpansionFraction() <= 0.01 && mShadeExpandedFraction < 1.0) {
-            mTracking = false;
+            setTracking(false);
         }
-        if (!isExpandImmediate() && mTracking) {
+        if (!isExpandImmediate() && isTracking()) {
             onTouch(event);
             if (!mConflictingExpansionGesture && !mSplitShadeEnabled) {
                 return true;
@@ -1662,7 +1664,7 @@
             if (shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
                 mShadeLog.logMotionEvent(event,
                         "handleQsDown: down action, QS tracking enabled");
-                mTracking = true;
+                setTracking(true);
                 onExpansionStarted();
                 mInitialHeightOnTouch = mExpansionHeight;
                 mInitialTouchY = event.getY();
@@ -1688,7 +1690,7 @@
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
                 mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
-                mTracking = true;
+                setTracking(true);
                 traceQsJank(true, false);
                 mInitialTouchY = y;
                 mInitialTouchX = x;
@@ -1725,7 +1727,7 @@
             case MotionEvent.ACTION_CANCEL:
                 mShadeLog.logMotionEvent(event,
                         "onQsTouch: up/cancel action, QS tracking disabled");
-                mTracking = false;
+                setTracking(false);
                 mTrackingPointer = -1;
                 trackMovement(event);
                 float fraction = computeExpansionFraction();
@@ -1780,7 +1782,7 @@
                     mInitialHeightOnTouch = mExpansionHeight;
                     mShadeLog.logMotionEvent(event,
                             "onQsIntercept: down action, QS tracking enabled");
-                    mTracking = true;
+                    setTracking(true);
                     traceQsJank(true, false);
                     mNotificationStackScrollLayoutController.cancelLongPress();
                 }
@@ -1799,7 +1801,7 @@
             case MotionEvent.ACTION_MOVE:
                 final float h = y - mInitialTouchY;
                 trackMovement(event);
-                if (mTracking) {
+                if (isTracking()) {
                     // Already tracking because onOverscrolled was called. We need to update here
                     // so we don't stop for a frame until the next touch event gets handled in
                     // onTouchEvent.
@@ -1819,7 +1821,7 @@
                         mInitialTouchX, mInitialTouchY, h)) {
                     mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
-                    mTracking = true;
+                    setTracking(true);
                     traceQsJank(true, false);
                     onExpansionStarted();
                     mPanelViewControllerLazy.get().notifyExpandingFinished();
@@ -1839,7 +1841,7 @@
             case MotionEvent.ACTION_UP:
                 trackMovement(event);
                 mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
-                mTracking = false;
+                setTracking(false);
                 break;
         }
         return false;
@@ -2065,7 +2067,7 @@
         ipw.print("mTouchAboveFalsingThreshold=");
         ipw.println(mTouchAboveFalsingThreshold);
         ipw.print("mTracking=");
-        ipw.println(mTracking);
+        ipw.println(isTracking());
         ipw.print("mTrackingPointer=");
         ipw.println(mTrackingPointer);
         ipw.print("mExpanded=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index 6564118..9a356ad 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -135,7 +135,8 @@
     private val date: TextView = header.requireViewById(R.id.date)
     private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
     private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
-    private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
+    private val systemIconsHoverContainer: View =
+        header.requireViewById(R.id.hover_system_icons_container)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -259,14 +260,18 @@
                     header.paddingRight,
                     header.paddingBottom
                 )
-                systemIcons.setPaddingRelative(
+                systemIconsHoverContainer.setPaddingRelative(
                     resources.getDimensionPixelSize(
-                        R.dimen.shade_header_system_icons_padding_start
+                        R.dimen.hover_system_icons_container_padding_start
                     ),
-                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_top),
-                    resources.getDimensionPixelSize(R.dimen.shade_header_system_icons_padding_end),
                     resources.getDimensionPixelSize(
-                        R.dimen.shade_header_system_icons_padding_bottom
+                        R.dimen.hover_system_icons_container_padding_top
+                    ),
+                    resources.getDimensionPixelSize(
+                        R.dimen.hover_system_icons_container_padding_end
+                    ),
+                    resources.getDimensionPixelSize(
+                        R.dimen.hover_system_icons_container_padding_bottom
                     )
                 )
             }
@@ -330,8 +335,8 @@
         demoModeController.addCallback(demoModeReceiver)
         statusBarIconController.addIconGroup(iconManager)
         nextAlarmController.addCallback(nextAlarmCallback)
-        systemIcons.setOnHoverListener(
-            statusOverlayHoverListenerFactory.createListener(systemIcons)
+        systemIconsHoverContainer.setOnHoverListener(
+            statusOverlayHoverListenerFactory.createListener(systemIconsHoverContainer)
         )
     }
 
@@ -343,7 +348,7 @@
         demoModeController.removeCallback(demoModeReceiver)
         statusBarIconController.removeIconGroup(iconManager)
         nextAlarmController.removeCallback(nextAlarmCallback)
-        systemIcons.setOnHoverListener(null)
+        systemIconsHoverContainer.setOnHoverListener(null)
     }
 
     fun disable(state1: Int, state2: Int, animate: Boolean) {
@@ -479,11 +484,11 @@
         if (largeScreenActive) {
             logInstantEvent("Large screen constraints set")
             header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
-            systemIcons.setOnClickListener { shadeCollapseAction?.run() }
+            systemIconsHoverContainer.setOnClickListener { shadeCollapseAction?.run() }
         } else {
             logInstantEvent("Small screen constraints set")
             header.setTransition(HEADER_TRANSITION_ID)
-            systemIcons.setOnClickListener(null)
+            systemIconsHoverContainer.setOnClickListener(null)
         }
         header.jumpToState(header.startState)
         updatePosition()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 947259a..52a99af 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -56,6 +56,30 @@
     @Deprecated("Use ShadeInteractor.shadeExpansion instead")
     val legacyShadeExpansion: StateFlow<Float>
 
+    /**
+     * NotificationPanelViewController.mTracking as a flow. "Tracking" means that the user is moving
+     * the shade up or down with a pointer. Going forward, this concept will be replaced by checks
+     * for whether a transition was driven by user input instead of whether a pointer is currently
+     * touching the screen, i.e. after the user has lifted their finger to fling the shade, these
+     * values would be different.
+     */
+    @Deprecated("Use ShadeInteractor instead") val legacyShadeTracking: StateFlow<Boolean>
+
+    /**
+     * QuickSettingsController.mTracking as a flow. "Tracking" means that the user is moving quick
+     * settings up or down with a pointer. Going forward, this concept will be replaced by checks
+     * for whether a transition was driven by user input instead of whether a pointer is currently
+     * touching the screen, i.e. after the user has lifted their finger to fling the QS, these
+     * values would be different.
+     */
+    @Deprecated("Use ShadeInteractor instead") val legacyQsTracking: StateFlow<Boolean>
+
+    /** Sets whether the user is moving Quick Settings with a pointer */
+    fun setLegacyQsTracking(legacyQsTracking: Boolean)
+
+    /** Sets whether the user is moving the shade with a pointer */
+    fun setLegacyShadeTracking(tracking: Boolean)
+
     /** Amount shade has expanded with regard to the UDFPS location */
     val udfpsTransitionToFullShadeProgress: StateFlow<Float>
 
@@ -123,6 +147,24 @@
     @Deprecated("Use ShadeInteractor.shadeExpansion instead")
     override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow()
 
+    private val _legacyShadeTracking = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow()
+
+    private val _legacyQsTracking = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyQsTracking: StateFlow<Boolean> = _legacyQsTracking.asStateFlow()
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyQsTracking(legacyQsTracking: Boolean) {
+        _legacyQsTracking.value = legacyQsTracking
+    }
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyShadeTracking(tracking: Boolean) {
+        _legacyShadeTracking.value = tracking
+    }
+
     override fun setQsExpansion(qsExpansion: Float) {
         _qsExpansion.value = qsExpansion
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 95a072c..b77b9e4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -35,15 +35,20 @@
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.isActive
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.isActive
 
 /** Business logic for shade interactions. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -119,18 +124,41 @@
             repository.qsExpansion
         }
 
-    /** The amount [0-1] either QS or the shade has been opened */
+    /** The amount [0-1] either QS or the shade has been opened. */
     val anyExpansion: StateFlow<Float> =
         combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
     /** Whether either the shade or QS is expanding from a fully collapsed state. */
-    val anyExpanding =
+    val isAnyExpanding =
         anyExpansion
             .pairwise(1f)
             .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
             .distinctUntilChanged()
 
+    /**
+     * Whether the user is expanding or collapsing the shade with user input. This will be true even
+     * if the user's input gesture has ended but a transition they initiated is animating.
+     */
+    val isUserInteractingWithShade: Flow<Boolean> =
+        userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion)
+
+    /**
+     * Whether the user is expanding or collapsing quick settings with user input. This will be true
+     * even if the user's input gesture has ended but a transition they initiated is still
+     * animating.
+     */
+    val isUserInteractingWithQs: Flow<Boolean> =
+        userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
+
+    /**
+     * Whether the user is expanding or collapsing either the shade or quick settings with user
+     * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
+     * but a transition they initiated is still animating.
+     */
+    val isUserInteracting: Flow<Boolean> =
+        combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs }
+
     /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
     val isExpandToQsEnabled: Flow<Boolean> =
         combine(
@@ -169,4 +197,30 @@
                 }
             }
             .distinctUntilChanged()
+
+    /**
+     * Return a flow for whether a user is interacting with an expandable shade component using
+     * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
+     * [expansion.first] checks the current value of the flow.
+     */
+    private fun userInteractingFlow(
+        tracking: Flow<Boolean>,
+        expansion: StateFlow<Float>
+    ): Flow<Boolean> {
+        return flow {
+            // initial value is false
+            emit(false)
+            while (currentCoroutineContext().isActive) {
+                // wait for tracking to become true
+                tracking.first { it }
+                emit(true)
+                // wait for tracking to become false
+                tracking.first { !it }
+                // wait for expansion to complete in either direction
+                expansion.first { it <= 0f || it >= 1f }
+                // interaction complete
+                emit(false)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index e206141..70ccc4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -31,6 +31,7 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -38,6 +39,7 @@
 import com.android.systemui.statusbar.notification.NotificationClicker;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.icon.IconManager;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
@@ -151,6 +153,7 @@
                                 component.getExpandableNotificationRowController();
                         rowController.init(entry);
                         entry.setRowController(rowController);
+                        maybeSetBigPictureIconManager(row, component);
                         bindRow(entry, row);
                         updateRow(entry, row);
                         inflateContentViews(entry, params, row, callback);
@@ -165,6 +168,7 @@
             return;
         }
         mLogger.logReleasingViews(entry);
+        cancelRunningJobs(entry.getRow());
         final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
         params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
@@ -172,6 +176,23 @@
         mRowContentBindStage.requestRebind(entry, null);
     }
 
+    private void maybeSetBigPictureIconManager(ExpandableNotificationRow row,
+            ExpandableNotificationRowComponent component) {
+        if (mFeatureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+            row.setBigPictureIconManager(component.getBigPictureIconManager());
+        }
+    }
+
+    private void cancelRunningJobs(ExpandableNotificationRow row) {
+        if (row == null) {
+            return;
+        }
+        BigPictureIconManager iconManager = row.getBigPictureIconManager();
+        if (iconManager != null) {
+            iconManager.cancelJobs();
+        }
+    }
+
     /**
      * Bind row to various controllers and managers. This is only called when the row is first
      * created.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
new file mode 100644
index 0000000..88dbb4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2023 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.row
+
+import android.annotation.WorkerThread
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Dumpable
+import android.util.Log
+import android.util.Size
+import com.android.internal.R
+import com.android.internal.widget.NotificationDrawableConsumer
+import com.android.internal.widget.NotificationIconManager
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Empty
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.FullImage
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.PlaceHolder
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+private const val TAG = "BigPicImageLoader"
+private const val DEBUG = false
+private const val FREE_IMAGE_DELAY_MS = 3000L
+
+/**
+ * A helper class for [com.android.internal.widget.BigPictureNotificationImageView] to lazy-load
+ * images from SysUI. It replaces the placeholder image with the fully loaded one, and vica versa.
+ *
+ * TODO(b/283082473) move the logs to a [com.android.systemui.log.LogBuffer]
+ */
+@SuppressWarnings("DumpableNotRegistered")
+class BigPictureIconManager
+@Inject
+constructor(
+    private val context: Context,
+    private val imageLoader: ImageLoader,
+    @Application private val scope: CoroutineScope,
+    @Main private val mainDispatcher: CoroutineDispatcher,
+    @Background private val bgDispatcher: CoroutineDispatcher
+) : NotificationIconManager, Dumpable {
+
+    private var lastLoadingJob: Job? = null
+    private var drawableConsumer: NotificationDrawableConsumer? = null
+    private var displayedState: DrawableState = Empty(null)
+    private var viewShown = false
+
+    private var maxWidth = getMaxWidth()
+    private var maxHeight = getMaxHeight()
+
+    /**
+     * Called when the displayed state changes of the view.
+     *
+     * @param shown true if the view is shown, and the image needs to be displayed.
+     */
+    fun onViewShown(shown: Boolean) {
+        log("onViewShown:$shown")
+
+        if (this.viewShown != shown) {
+            this.viewShown = shown
+
+            val state = displayedState
+
+            this.lastLoadingJob?.cancel()
+            this.lastLoadingJob =
+                when {
+                    state is Empty && shown -> state.icon?.let(::startLoadingJob)
+                    state is PlaceHolder && shown -> startLoadingJob(state.icon)
+                    state is FullImage && !shown ->
+                        startFreeImageJob(state.icon, state.drawableSize)
+                    else -> null
+                }
+        }
+    }
+
+    /**
+     * Update the maximum width and height allowed for bitmaps, ex. after a configuration change.
+     */
+    fun updateMaxImageSizes() {
+        log("updateMaxImageSizes")
+        maxWidth = getMaxWidth()
+        maxHeight = getMaxHeight()
+    }
+
+    /** Cancels all currently running jobs. */
+    fun cancelJobs() {
+        lastLoadingJob?.cancel()
+    }
+
+    @WorkerThread
+    override fun updateIcon(drawableConsumer: NotificationDrawableConsumer, icon: Icon?): Runnable {
+        if (this.drawableConsumer != null && this.drawableConsumer != drawableConsumer) {
+            Log.wtf(TAG, "A consumer is already set for this iconManager.")
+            return Runnable {}
+        }
+
+        if (displayedState.iconSameAs(icon)) {
+            // We're already handling this icon, nothing to do here.
+            log("skipping updateIcon for consumer:$drawableConsumer with icon:$icon")
+            return Runnable {}
+        }
+
+        this.drawableConsumer = drawableConsumer
+        this.displayedState = Empty(icon)
+        this.lastLoadingJob?.cancel()
+
+        val drawable = loadImageOrPlaceHolderSync(icon)
+
+        log("icon updated")
+
+        return Runnable { drawableConsumer.setImageDrawable(drawable) }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>?) {
+        pw.println("BigPictureIconManager ${getDebugString()}")
+    }
+
+    @WorkerThread
+    private fun loadImageOrPlaceHolderSync(icon: Icon?): Drawable? {
+        icon ?: return null
+
+        if (viewShown) {
+            return loadImageSync(icon)
+        }
+
+        return loadPlaceHolderSync(icon) ?: loadImageSync(icon)
+    }
+
+    @WorkerThread
+    private fun loadImageSync(icon: Icon): Drawable? {
+        return imageLoader.loadDrawableSync(icon, context, maxWidth, maxHeight)?.also { drawable ->
+            checkPlaceHolderSizeForDrawable(this.displayedState, drawable)
+            this.displayedState = FullImage(icon, drawable.intrinsicSize)
+        }
+    }
+
+    private fun checkPlaceHolderSizeForDrawable(
+        displayedState: DrawableState,
+        newDrawable: Drawable
+    ) {
+        if (displayedState is PlaceHolder) {
+            val (oldWidth, oldHeight) = displayedState.drawableSize
+            val (newWidth, newHeight) = newDrawable.intrinsicSize
+
+            if (oldWidth != newWidth || oldHeight != newHeight) {
+                Log.e(
+                    TAG,
+                    "Mismatch in dimensions, when replacing PlaceHolder " +
+                        "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+                )
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun loadPlaceHolderSync(icon: Icon): Drawable? {
+        return imageLoader
+            .loadSizeSync(icon, context)
+            ?.resizeToMax(maxWidth, maxHeight) // match the dimensions of the fully loaded drawable
+            ?.let { size -> createPlaceHolder(size) }
+            ?.also { drawable -> this.displayedState = PlaceHolder(icon, drawable.intrinsicSize) }
+    }
+
+    private fun startLoadingJob(icon: Icon): Job =
+        scope.launch {
+            val drawable = withContext(bgDispatcher) { loadImageSync(icon) }
+            withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) }
+            log("image loaded")
+        }
+
+    private fun startFreeImageJob(icon: Icon, drawableSize: Size): Job =
+        scope.launch {
+            delay(FREE_IMAGE_DELAY_MS)
+            val drawable = createPlaceHolder(drawableSize)
+            displayedState = PlaceHolder(icon, drawable.intrinsicSize)
+            withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) }
+            log("placeholder loaded")
+        }
+
+    private fun createPlaceHolder(size: Size): Drawable {
+        return PlaceHolderDrawable(width = size.width, height = size.height)
+    }
+
+    private fun isLowRam(): Boolean {
+        return ActivityManager.isLowRamDeviceStatic()
+    }
+
+    private fun getMaxWidth() =
+        context.resources.getDimensionPixelSize(
+            if (isLowRam()) {
+                R.dimen.notification_big_picture_max_width_low_ram
+            } else {
+                R.dimen.notification_big_picture_max_width
+            }
+        )
+
+    private fun getMaxHeight() =
+        context.resources.getDimensionPixelSize(
+            if (isLowRam()) {
+                R.dimen.notification_big_picture_max_height_low_ram
+            } else {
+                R.dimen.notification_big_picture_max_height
+            }
+        )
+
+    private fun log(msg: String) {
+        if (DEBUG) {
+            Log.d(TAG, "$msg state=${getDebugString()}")
+        }
+    }
+
+    private fun getDebugString() =
+        "{ state:$displayedState, hasConsumer:${drawableConsumer != null}, viewShown:$viewShown}"
+
+    private sealed class DrawableState(open val icon: Icon?) {
+        data class Empty(override val icon: Icon?) : DrawableState(icon)
+        data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
+            DrawableState(icon)
+        data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
+
+        fun iconSameAs(other: Icon?): Boolean {
+            val displayedIcon = icon
+            return when {
+                displayedIcon == null && other == null -> true
+                displayedIcon != null && other != null -> displayedIcon.sameAs(other)
+                else -> false
+            }
+        }
+    }
+}
+
+/**
+ * @return an image size that conforms to the maxWidth / maxHeight parameters. It can be the same
+ *   instance, if the provided size was already small enough.
+ */
+private fun Size.resizeToMax(maxWidth: Int, maxHeight: Int): Size {
+    if (width <= maxWidth && height <= maxHeight) {
+        return this
+    }
+
+    // Calculate the scale factor for both dimensions
+    val wScale =
+        if (maxWidth <= 0) {
+            1.0f
+        } else {
+            maxWidth.toFloat() / width.toFloat()
+        }
+
+    val hScale =
+        if (maxHeight <= 0) {
+            1.0f
+        } else {
+            maxHeight.toFloat() / height.toFloat()
+        }
+
+    // Scale down to the smaller scale factor
+    val scale = min(wScale, hScale)
+    if (scale < 1.0f) {
+        val targetWidth = (width * scale).toInt()
+        val targetHeight = (height * scale).toInt()
+
+        return Size(targetWidth, targetHeight)
+    }
+
+    return this
+}
+
+private val Drawable.intrinsicSize
+    get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+
+private operator fun Size.component1() = width
+
+private operator fun Size.component2() = height
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt
new file mode 100644
index 0000000..e226665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.internal.widget.BigPictureNotificationImageView
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import javax.inject.Inject
+
+class BigPictureLayoutInflaterFactory @Inject constructor() : NotifRemoteViewsFactory {
+
+    override fun instantiate(
+        row: ExpandableNotificationRow,
+        @InflationFlag layoutType: Int,
+        parent: View?,
+        name: String,
+        context: Context,
+        attrs: AttributeSet
+    ): View? {
+        // Currently the [BigPictureIconManager] only handles one view per notification.
+        // Exclude other layout types for now, to make sure that we set the same iconManager
+        // on only one [BigPictureNotificationImageView].
+        if (layoutType != FLAG_CONTENT_VIEW_EXPANDED) {
+            return null
+        }
+
+        return when (name) {
+            BigPictureNotificationImageView::class.java.name ->
+                BigPictureNotificationImageView(context, attrs).also { view ->
+                    view.setIconManager(row.bigPictureIconManager)
+                }
+            else -> null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index c02382d..7fa955b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -376,6 +376,7 @@
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
     private NotificationInlineImageResolver mImageResolver;
+    private BigPictureIconManager mBigPictureIconManager;
     @Nullable
     private OnExpansionChangedListener mExpansionChangedListener;
     @Nullable
@@ -1355,6 +1356,9 @@
         if (mImageResolver != null) {
             mImageResolver.updateMaxImageSizes();
         }
+        if (mBigPictureIconManager != null) {
+            mBigPictureIconManager.updateMaxImageSizes();
+        }
     }
 
     public void onUiModeChanged() {
@@ -1794,6 +1798,16 @@
         return mImageResolver;
     }
 
+    public BigPictureIconManager getBigPictureIconManager() {
+        return mBigPictureIconManager;
+    }
+
+    public void setBigPictureIconManager(
+            BigPictureIconManager bigPictureIconManager) {
+        mBigPictureIconManager = bigPictureIconManager;
+    }
+
+
     /**
      * Resets this view so it can be re-used for an updated notification.
      */
@@ -3687,6 +3701,9 @@
                 pw.println("no viewState!!!");
             }
             pw.println(getRoundableState().debugString());
+            if (mBigPictureIconManager != null) {
+                mBigPictureIconManager.dump(pw, args);
+            }
             dumpBackgroundView(pw, args);
 
             int transientViewCount = mChildrenContainer == null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 867e08b..0239afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -60,12 +60,16 @@
     @Named(NOTIF_REMOTEVIEWS_FACTORIES)
     static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
             FeatureFlags featureFlags,
-            PrecomputedTextViewFactory precomputedTextViewFactory
+            PrecomputedTextViewFactory precomputedTextViewFactory,
+            BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory
     ) {
         final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
         if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
             replacementFactories.add(precomputedTextViewFactory);
         }
+        if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+            replacementFactories.add(bigPictureLayoutInflaterFactory);
+        }
         return replacementFactories;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt
new file mode 100644
index 0000000..40aa27f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt
@@ -0,0 +1,33 @@
+package com.android.systemui.statusbar.notification.row
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+
+class PlaceHolderDrawable(private val width: Int, private val height: Int) : Drawable() {
+
+    companion object {
+        fun createFrom(other: Drawable): PlaceHolderDrawable {
+            return PlaceHolderDrawable(other.intrinsicWidth, other.intrinsicHeight)
+        }
+    }
+
+    override fun getIntrinsicWidth(): Int {
+        return width
+    }
+
+    override fun getIntrinsicHeight(): Int {
+        return height
+    }
+
+    override fun draw(canvas: Canvas) {}
+    override fun setAlpha(alpha: Int) {}
+    override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+    @Suppress("DeprecatedCallableAddReplaceWith")
+    @Deprecated("Deprecated in android.graphics.drawable.Drawable")
+    override fun getOpacity(): Int {
+        return PixelFormat.TRANSPARENT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 3588621..0a6a2c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -23,6 +23,7 @@
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -68,6 +69,12 @@
     ExpandableNotificationRowController getExpandableNotificationRowController();
 
     /**
+     * Creates a BigPictureIconManager.
+     */
+    @NotificationRowScope
+    BigPictureIconManager getBigPictureIconManager();
+
+    /**
      * Dagger Module that extracts interesting properties from an ExpandableNotificationRow.
      */
     @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 175ba15..acd6cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -28,6 +28,7 @@
 import com.android.internal.R;
 import com.android.internal.widget.BigPictureNotificationImageView;
 import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 
 /**
@@ -66,6 +67,17 @@
         }
     }
 
+    @Override
+    public void setVisible(boolean visible) {
+        super.setVisible(visible);
+
+        BigPictureIconManager imageManager = mRow.getBigPictureIconManager();
+        if (imageManager != null) {
+            // TODO(b/283082473) call it a bit earlier for true, as soon as the row starts to expand
+            imageManager.onViewShown(visible);
+        }
+    }
+
     /**
      * Starts or stops the animations in any drawables contained in this BigPicture Notification.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index cfa481e..7d57568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -269,8 +269,6 @@
 
     boolean isScreenFullyOff();
 
-    void showScreenPinningRequest(int taskId, boolean allowCancel);
-
     @Nullable
     Intent getEmergencyActionIntent();
 
@@ -301,8 +299,6 @@
 
     boolean isBouncerShowingScrimmed();
 
-    boolean isBouncerShowingOverDream();
-
     void updateNotificationPanelTouchState();
 
     int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 28bb581..ebcfb8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -59,6 +59,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.QuickSettingsController;
@@ -84,6 +85,7 @@
 public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
     private final CentralSurfaces mCentralSurfaces;
     private final Context mContext;
+    private final ScreenPinningRequest mScreenPinningRequest;
     private final com.android.systemui.shade.ShadeController mShadeController;
     private final CommandQueue mCommandQueue;
     private final ShadeViewController mShadeViewController;
@@ -126,6 +128,7 @@
             QuickSettingsController quickSettingsController,
             Context context,
             @Main Resources resources,
+            ScreenPinningRequest screenPinningRequest,
             ShadeController shadeController,
             CommandQueue commandQueue,
             ShadeViewController shadeViewController,
@@ -155,6 +158,7 @@
         mCentralSurfaces = centralSurfaces;
         mQsController = quickSettingsController;
         mContext = context;
+        mScreenPinningRequest = screenPinningRequest;
         mShadeController = shadeController;
         mCommandQueue = commandQueue;
         mShadeViewController = shadeViewController;
@@ -516,7 +520,7 @@
             return;
         }
         // Show screen pinning request, since this comes from an app, show 'no thanks', button.
-        mCentralSurfaces.showScreenPinningRequest(taskId, true);
+        mScreenPinningRequest.showPrompt(taskId, true);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index ff380db..5e505f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -71,7 +71,6 @@
     override fun isOverviewEnabled() = false
     override fun setBouncerShowing(bouncerShowing: Boolean) {}
     override fun isScreenFullyOff() = false
-    override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {}
     override fun getEmergencyActionIntent(): Intent? = null
     override fun isCameraAllowedByAdmin() = false
     override fun isGoingToSleep() = false
@@ -84,7 +83,6 @@
     override fun awakenDreams() {}
     override fun isBouncerShowing() = false
     override fun isBouncerShowingScrimmed() = false
-    override fun isBouncerShowingOverDream() = false
     override fun updateNotificationPanelTouchState() {}
     override fun getRotation() = 0
     override fun setBarStateForTest(state: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 490c469..8d35d39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,7 +168,6 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.UserTracker;
@@ -388,7 +387,6 @@
      */
     protected int mState; // TODO: remove this. Just use StatusBarStateController
     protected boolean mBouncerShowing;
-    private boolean mBouncerShowingOverDream;
 
     private final PhoneStatusBarPolicy mIconPolicy;
 
@@ -507,8 +505,6 @@
         ? new GestureRecorder("/sdcard/statusbar_gestures.dat")
         : null;
 
-    private final ScreenPinningRequest mScreenPinningRequest;
-
     private final MetricsLogger mMetricsLogger;
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
@@ -692,7 +688,6 @@
             DozeServiceHost dozeServiceHost,
             BackActionInteractor backActionInteractor,
             PowerManager powerManager,
-            ScreenPinningRequest screenPinningRequest,
             DozeScrimController dozeScrimController,
             VolumeComponent volumeComponent,
             CommandQueue commandQueue,
@@ -799,7 +794,6 @@
         mDozeParameters = dozeParameters;
         mScrimController = scrimController;
         mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
-        mScreenPinningRequest = screenPinningRequest;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
         mAuthRippleController = authRippleController;
@@ -2460,10 +2454,7 @@
      */
     @Override
     public boolean shouldKeyguardHideImmediately() {
-        final boolean isScrimmedBouncer =
-                mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
-        final boolean isBouncerOverDream = isBouncerShowingOverDream();
-        return (isScrimmedBouncer || isBouncerOverDream);
+        return mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
     }
 
     private void showBouncerOrLockScreenIfKeyguard() {
@@ -2815,11 +2806,6 @@
         return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF;
     }
 
-    @Override
-    public void showScreenPinningRequest(int taskId, boolean allowCancel) {
-        mScreenPinningRequest.showPrompt(taskId, allowCancel);
-    }
-
     @Nullable
     @Override
     public Intent getEmergencyActionIntent() {
@@ -3160,11 +3146,6 @@
         return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming();
     }
 
-    @Override
-    public boolean isBouncerShowingOverDream() {
-        return mBouncerShowingOverDream;
-    }
-
     // End Extra BaseStatusBarMethods.
 
     boolean isTransientShown() {
@@ -3251,8 +3232,6 @@
             if (DEBUG) {
                 Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
             }
-
-            mScreenPinningRequest.onConfigurationChanged();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 47ab316..5de0d15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -62,6 +62,7 @@
     private var keyguardIndicationArea: View? = null
     private var binding: KeyguardBottomAreaViewBinder.Binding? = null
     private var lockIconViewController: LockIconViewController? = null
+    private var isLockscreenLandscapeEnabled: Boolean = false
 
     /** Initializes the view. */
     @Deprecated("Deprecated as part of b/278057014")
@@ -115,15 +116,26 @@
         }
     }
 
+    fun setIsLockscreenLandscapeEnabled(isLockscreenLandscapeEnabled: Boolean) {
+        this.isLockscreenLandscapeEnabled = isLockscreenLandscapeEnabled
+    }
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         ambientIndicationArea = findViewById(R.id.ambient_indication_container)
+        keyguardIndicationArea = findViewById(R.id.keyguard_indication_area)
     }
 
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         binding?.onConfigurationChanged()
 
+        if (isLockscreenLandscapeEnabled) {
+            updateIndicationAreaBottomMargin()
+        }
+    }
+
+    private fun updateIndicationAreaBottomMargin() {
         keyguardIndicationArea?.let {
             val params = it.layoutParams as FrameLayout.LayoutParams
             params.bottomMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
index 3942dae..0bad47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
@@ -16,11 +16,20 @@
 
 package com.android.systemui.statusbar.phone
 
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.util.ViewController
 import javax.inject.Inject
 
-class KeyguardBottomAreaViewController @Inject constructor(view: KeyguardBottomAreaView) :
+class KeyguardBottomAreaViewController
+    @Inject constructor(view: KeyguardBottomAreaView, featureFlags: FeatureFlagsClassic) :
     ViewController<KeyguardBottomAreaView> (view) {
+
+    init {
+        view.setIsLockscreenLandscapeEnabled(
+                featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE))
+    }
+
     override fun onViewAttached() {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be336e5..16413d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,6 +45,8 @@
 import com.android.systemui.R;
 import com.android.systemui.battery.BatteryMeterViewController;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
@@ -69,6 +71,8 @@
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.settings.SecureSettings;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -76,8 +80,6 @@
 
 import javax.inject.Inject;
 
-import kotlin.Unit;
-
 /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
 public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
     private static final String TAG = "KeyguardStatusBarViewController";
@@ -111,6 +113,7 @@
     private final BiometricUnlockController mBiometricUnlockController;
     private final SysuiStatusBarStateController mStatusBarStateController;
     private final StatusBarContentInsetsProvider mInsetsProvider;
+    private final FeatureFlags mFeatureFlags;
     private final UserManager mUserManager;
     private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
     private final SecureSettings mSecureSettings;
@@ -283,6 +286,7 @@
             BiometricUnlockController biometricUnlockController,
             SysuiStatusBarStateController statusBarStateController,
             StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+            FeatureFlags featureFlags,
             UserManager userManager,
             StatusBarUserChipViewModel userChipViewModel,
             SecureSettings secureSettings,
@@ -308,6 +312,7 @@
         mBiometricUnlockController = biometricUnlockController;
         mStatusBarStateController = statusBarStateController;
         mInsetsProvider = statusBarContentInsetsProvider;
+        mFeatureFlags = featureFlags;
         mUserManager = userManager;
         mStatusBarUserChipViewModel = userChipViewModel;
         mSecureSettings = secureSettings;
@@ -367,7 +372,19 @@
                     mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
             mTintedIconManager.setBlockList(getBlockedIcons());
             mStatusBarIconController.addIconGroup(mTintedIconManager);
+        } else {
+            // In the old implementation, the keyguard status bar view is never detached and
+            // re-attached, so only calling #addIconGroup when the IconManager is first created was
+            // safe and correct.
+            // In the new scene framework implementation, the keyguard status bar view *is* detached
+            // whenever the shade is opened on top of lockscreen, and then re-attached when the
+            // shade is closed. So, we need to re-add the IconManager each time we're re-attached to
+            // get icon updates.
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+                mStatusBarIconController.addIconGroup(mTintedIconManager);
+            }
         }
+
         mSystemIconsContainer = mView.findViewById(R.id.system_icons);
         StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory
                 .createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e337215..8d86d72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,7 +22,6 @@
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -472,17 +471,11 @@
         }
 
         if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            // Show the keyguard views whenever we've told WM that the lockscreen is visible.
             mShadeViewController.postToView(() ->
                     collectFlow(
                         getViewRootImpl().getView(),
-                        combineFlows(
-                                mKeyguardTransitionInteractor.getFinishedKeyguardState(),
-                                mWmLockscreenVisibilityInteractor.get()
-                                        .getUsingKeyguardGoingAwayAnimation(),
-                                (finishedState, animating) ->
-                                        KeyguardInteractor.Companion.isKeyguardVisibleInState(
-                                                finishedState)
-                                                || animating),
+                        mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
                         this::consumeShowStatusBarKeyguardView));
         }
     }
@@ -635,8 +628,12 @@
     protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
         if (needsFullscreenBouncer() && !mDozing) {
             // The keyguard might be showing (already). So we need to hide it.
-            mCentralSurfaces.hideKeyguard();
-            mPrimaryBouncerInteractor.show(true);
+            if (!primaryBouncerIsShowing()) {
+                mCentralSurfaces.hideKeyguard();
+                mPrimaryBouncerInteractor.show(true);
+            } else {
+                Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+            }
         } else {
             mCentralSurfaces.showKeyguard();
             if (hideBouncerWhenShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index dabdcc5..39cdfa3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -169,6 +169,7 @@
     @Override
     public void addCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
+            Log.d(TAG, "Added callback " + callback.getClass());
             mCallbacks.add(callback);
         }
     }
@@ -176,6 +177,7 @@
     @Override
     public void removeCallback(@NonNull Callback callback) {
         synchronized (mCallbacksLock) {
+            Log.d(TAG, "Removed callback " + callback.getClass());
             mCallbacks.remove(callback);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
index 81d04d4..6fd0a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.wallet.ui;
 
+import static com.android.systemui.wallet.util.WalletCardUtilsKt.getPaymentCards;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -47,10 +49,10 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /** Controller for the wallet card carousel screen. */
 public class WalletScreenController implements
@@ -126,22 +128,11 @@
             return;
         }
         Log.i(TAG, "Successfully retrieved wallet cards.");
-        List<WalletCard> walletCards = response.getWalletCards();
+        List<WalletCard> walletCards = getPaymentCards(response.getWalletCards());
 
-        boolean allUnknown = true;
-        for (WalletCard card : walletCards) {
-            if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) {
-                allUnknown = false;
-                break;
-            }
-        }
-
-        List<WalletCardViewInfo> paymentCardData = new ArrayList<>();
-        for (WalletCard card : walletCards) {
-            if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) {
-                paymentCardData.add(new QAWalletCardViewInfo(mContext, card));
-            }
-        }
+        List<WalletCardViewInfo> paymentCardData = walletCards.stream().map(
+                card -> new QAWalletCardViewInfo(mContext, card)
+        ).collect(Collectors.toList());
 
         // Get on main thread for UI updates.
         mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt b/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt
new file mode 100644
index 0000000..077b80b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wallet/util/WalletCardUtils.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.wallet.util
+
+import android.service.quickaccesswallet.WalletCard
+
+/**
+ * Filters wallet cards to only those of [WalletCard.CARD_TYPE_PAYMENT], or returns all cards if
+ * they are all of [WalletCard.CARD_TYPE_UNKNOWN] (maintaining pre-U behavior). Used by the wallet
+ * card carousel, quick settings tile, and lock screen.
+ */
+fun getPaymentCards(walletCards: List<WalletCard>): List<WalletCard> {
+    val atLeastOneKnownCardType = walletCards.any { it.cardType != WalletCard.CARD_TYPE_UNKNOWN }
+
+    return if (atLeastOneKnownCardType) {
+        walletCards.filter { it.cardType == WalletCard.CARD_TYPE_PAYMENT }
+    } else {
+        walletCards
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 6fafcd5..897c4da 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -61,6 +61,7 @@
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
 
@@ -109,6 +110,7 @@
     private final Optional<SplitScreen> mSplitScreenOptional;
     private final Optional<OneHanded> mOneHandedOptional;
     private final Optional<DesktopMode> mDesktopModeOptional;
+    private final Optional<RecentTasks> mRecentTasksOptional;
 
     private final CommandQueue mCommandQueue;
     private final ConfigurationController mConfigurationController;
@@ -172,6 +174,7 @@
             Optional<SplitScreen> splitScreenOptional,
             Optional<OneHanded> oneHandedOptional,
             Optional<DesktopMode> desktopMode,
+            Optional<RecentTasks> recentTasks,
             CommandQueue commandQueue,
             ConfigurationController configurationController,
             KeyguardStateController keyguardStateController,
@@ -195,6 +198,7 @@
         mSplitScreenOptional = splitScreenOptional;
         mOneHandedOptional = oneHandedOptional;
         mDesktopModeOptional = desktopMode;
+        mRecentTasksOptional = recentTasks;
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mUserTracker = userTracker;
         mDisplayTracker = displayTracker;
@@ -220,6 +224,7 @@
         mSplitScreenOptional.ifPresent(this::initSplitScreen);
         mOneHandedOptional.ifPresent(this::initOneHanded);
         mDesktopModeOptional.ifPresent(this::initDesktopMode);
+        mRecentTasksOptional.ifPresent(this::initRecentTasks);
 
         mNoteTaskInitializer.initialize();
     }
@@ -351,6 +356,12 @@
                 }, mSysUiMainExecutor);
     }
 
+    @VisibleForTesting
+    void initRecentTasks(RecentTasks recentTasks) {
+        recentTasks.addAnimationStateListener(mSysUiMainExecutor,
+                mCommandQueue::onRecentsAnimationStateChanged);
+    }
+
     @Override
     public boolean isDumpCritical() {
         // Dump can't be critical because the shell has to dump on the main thread for
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index e1b608f..ee67348 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.whenever
 import org.junit.Before
@@ -67,6 +68,7 @@
     @Mock
     private lateinit var mKeyguardMessageAreaController:
         KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+    @Mock private lateinit var postureController: DevicePostureController
 
     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
 
@@ -89,6 +91,7 @@
         `when`(keyguardPasswordView.resources).thenReturn(context.resources)
         val fakeFeatureFlags = FakeFeatureFlags()
         fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+        fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
                 keyguardPasswordView,
@@ -104,6 +107,7 @@
                 mContext.resources,
                 falsingCollector,
                 keyguardViewController,
+                postureController,
                 fakeFeatureFlags
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 93048a5..0ef9f45 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -94,9 +94,10 @@
             .thenReturn(mKeyguardMessageAreaController)
         fakeFeatureFlags = FakeFeatureFlags()
         fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
+        fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
         mKeyguardPatternView =
             View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
-
+        mKeyguardPatternView.setIsLockScreenLandscapeEnabled(false)
         mKeyguardPatternViewController =
             KeyguardPatternViewController(
                 mKeyguardPatternView,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 33d4097..a9f044c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -123,7 +123,6 @@
     private fun constructPinViewController(
         mKeyguardPinView: KeyguardPINView
     ): KeyguardPinViewController {
-        mKeyguardPinView.setIsLockScreenLandscapeEnabled(false)
         return KeyguardPinViewController(
             mKeyguardPinView,
             keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 7c1861e..decc457 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -60,6 +60,7 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -140,6 +141,7 @@
     @Mock private lateinit var userInteractor: UserInteractor
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+    @Mock private lateinit var postureController: DevicePostureController
 
     @Captor
     private lateinit var swipeListenerArgumentCaptor:
@@ -197,6 +199,7 @@
         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
         featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
         featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+        featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -213,6 +216,7 @@
                 mock(),
                 null,
                 keyguardViewController,
+                postureController,
                 featureFlags
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index f9830b1..0b0410a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -59,6 +59,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -108,6 +109,7 @@
 
     private AppOpsControllerImpl mController;
     private TestableLooper mTestableLooper;
+    private final FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private String mExemptedRolePkgName;
 
@@ -139,6 +141,7 @@
         mController = new AppOpsControllerImpl(
                 mContext,
                 mTestableLooper.getLooper(),
+                mBgExecutor,
                 mDumpManager,
                 mAudioManager,
                 mSensorPrivacyController,
@@ -150,6 +153,8 @@
     @Test
     public void testOnlyListenForFewOps() {
         mController.setListening(true);
+        mBgExecutor.runAllReady();
+
         verify(mAppOpsManager, times(1)).startWatchingActive(AppOpsControllerImpl.OPS, mController);
         verify(mDispatcher, times(1)).registerReceiverWithHandler(eq(mController), any(), any());
         verify(mSensorPrivacyController, times(1)).addCallback(mController);
@@ -158,6 +163,7 @@
     @Test
     public void testStopListening() {
         mController.setListening(false);
+        mBgExecutor.runAllReady();
         verify(mAppOpsManager, times(1)).stopWatchingActive(mController);
         verify(mDispatcher, times(1)).unregisterReceiver(mController);
         verify(mSensorPrivacyController, times(1)).removeCallback(mController);
@@ -169,6 +175,7 @@
                 .thenReturn(List.of());
 
         mController.setListening(true);
+        mBgExecutor.runAllReady();
 
         assertThat(mController.getActiveAppOps()).isEmpty();
     }
@@ -186,6 +193,7 @@
 
         // WHEN we start listening
         mController.setListening(true);
+        mBgExecutor.runAllReady();
 
         // THEN the active list has the op
         List<AppOpItem> list = mController.getActiveAppOps();
@@ -218,6 +226,7 @@
 
         // WHEN we start listening
         mController.setListening(true);
+        mBgExecutor.runAllReady();
 
         // THEN the active list has the ops
         List<AppOpItem> list = mController.getActiveAppOps();
@@ -265,6 +274,7 @@
 
         // WHEN we start listening
         mController.setListening(true);
+        mBgExecutor.runAllReady();
 
         // THEN the active list has the ops
         List<AppOpItem> list = mController.getActiveAppOps();
@@ -304,6 +314,7 @@
 
         // WHEN we start listening
         mController.setListening(true);
+        mBgExecutor.runAllReady();
 
         // THEN the active list has the ops
         List<AppOpItem> list = mController.getActiveAppOps();
@@ -341,6 +352,7 @@
         mController.addCallback(
                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
                 mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
 
         // THEN the callback is notified of the current active ops it cares about
@@ -366,11 +378,14 @@
         mController.addCallback(
                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
                 mCallback);
+        mBgExecutor.runAllReady();
+
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
         mTestableLooper.processAllMessages();
+
         verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
                 TEST_UID, TEST_PACKAGE_NAME, true);
     }
@@ -383,6 +398,7 @@
     public void addCallback_partialIncludedCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
                 AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mController.onOpActiveChanged(
@@ -400,9 +416,12 @@
     @Test
     public void addCallback_notIncludedCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
+
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mTestableLooper.processAllMessages();
+
         verify(mCallback, never()).onActiveStateChanged(
                 anyInt(), anyInt(), anyString(), anyBoolean());
     }
@@ -410,7 +429,10 @@
     @Test
     public void removeCallback_sameCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
+
         mController.removeCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mTestableLooper.processAllMessages();
@@ -421,7 +443,10 @@
     @Test
     public void addCallback_notSameCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
+
         mController.removeCallback(new int[]{AppOpsManager.OP_CAMERA}, mCallback);
+        mBgExecutor.runAllReady();
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
         mTestableLooper.processAllMessages();
@@ -484,6 +509,8 @@
         assumeFalse(mExemptedRolePkgName == null || mExemptedRolePkgName.equals(""));
 
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
+
         mController.onOpActiveChanged(AppOpsManager.OPSTR_RECORD_AUDIO,
                 TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
 
@@ -506,6 +533,7 @@
         mController.setBGHandler(mMockHandler);
 
         mController.setListening(true);
+        mBgExecutor.runAllReady();
         mController.onOpActiveChanged(AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID,
                 TEST_PACKAGE_NAME, true);
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -513,6 +541,7 @@
         assertFalse(mController.getActiveAppOps().isEmpty());
 
         mController.setListening(false);
+        mBgExecutor.runAllReady();
 
         verify(mMockHandler).removeCallbacksAndMessages(null);
         assertTrue(mController.getActiveAppOps().isEmpty());
@@ -583,6 +612,7 @@
         TestHandler testHandler = new TestHandler(mTestableLooper.getLooper());
 
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
         mController.setBGHandler(testHandler);
 
         mController.onOpActiveChanged(
@@ -616,6 +646,7 @@
     @Test
     public void testNotedNotRemovedAfterActive() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
@@ -645,6 +676,7 @@
     @Test
     public void testNotedAndActiveOnlyOneCall() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
 
         mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
                 TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
@@ -660,6 +692,7 @@
     @Test
     public void testActiveAndNotedOnlyOneCall() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mBgExecutor.runAllReady();
 
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
@@ -675,6 +708,7 @@
     @Test
     public void testPausedRecordingIsRetrievedOnCreation() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
 
         mController.onOpActiveChanged(
@@ -688,6 +722,7 @@
     @Test
     public void testPausedRecordingFilteredOut() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
 
         mController.onOpActiveChanged(
@@ -700,6 +735,7 @@
     @Test
     public void testPausedPhoneCallMicrophoneFilteredOut() {
         mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
 
         mController.onOpActiveChanged(
@@ -715,6 +751,7 @@
                 AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_CAMERA
         }, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
 
         mController.onOpActiveChanged(
@@ -751,6 +788,7 @@
         // Add callbacks for the micOp and nonMicOp, called for the micOp active state change,
         // verify the micOp is the only active op returned.
         mController.addCallback(new int[]{micOp, nonMicOp}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.opToPublicName(micOp), TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -779,6 +817,7 @@
         // Add callbacks for the micOp and nonMicOp, called for the micOp active state change,
         // verify the micOp is the only active op returned.
         mController.addCallback(new int[]{micOp, nonMicOp}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.opToPublicName(micOp), TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -807,6 +846,7 @@
         // Add callbacks for the micOp and nonMicOp, called for the micOp active state change,
         // verify the micOp is the only active op returned.
         mController.addCallback(new int[]{micOp, nonMicOp}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.opToPublicName(micOp), TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -835,6 +875,7 @@
         // Add callbacks for the micOp and nonMicOp, called for the micOp active state change,
         // verify the micOp is the only active op returned.
         mController.addCallback(new int[]{micOp, nonMicOp}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.opToPublicName(micOp), TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -859,6 +900,7 @@
     public void testCameraFilteredWhenCameraDisabled() {
         mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
                 mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -892,6 +934,7 @@
         mController.addCallback(
                 new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA},
                 mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(
                 AppOpsManager.OPSTR_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
@@ -922,6 +965,7 @@
 
     private void verifyUnPausedSentActive(int micOpCode) {
         mController.addCallback(new int[]{micOpCode}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID,
                 TEST_PACKAGE_NAME, true);
@@ -936,6 +980,7 @@
 
     private void verifyAudioPausedSentInactive(int micOpCode) {
         mController.addCallback(new int[]{micOpCode}, mCallback);
+        mBgExecutor.runAllReady();
         mTestableLooper.processAllMessages();
         mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID_OTHER,
                 TEST_PACKAGE_NAME, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 3a0883b..511562f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -21,6 +21,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.FlowValue
@@ -62,6 +63,7 @@
     @Before
     fun setup() {
         setDisplays(emptyList())
+        setAllDisplaysIncludingDisabled()
         displayRepository =
             DisplayRepositoryImpl(
                 displayManager,
@@ -70,6 +72,7 @@
                 UnconfinedTestDispatcher()
             )
         verify(displayManager, never()).registerDisplayListener(any(), any())
+        verify(displayManager, never()).getDisplays(any())
     }
 
     @Test
@@ -351,6 +354,22 @@
         }
 
     @Test
+    fun initialState_onePendingDisplayOnBoot_notNull() =
+        testScope.runTest {
+            // 1 is not enabled, but just connected. It should be seen as pending
+            setAllDisplaysIncludingDisabled(0, 1)
+            setDisplays(0) // 0 is enabled.
+            verify(displayManager, never()).getDisplays(any())
+
+            val pendingDisplay by collectLastValue(displayRepository.pendingDisplay)
+
+            verify(displayManager).getDisplays(any())
+
+            assertThat(pendingDisplay).isNotNull()
+            assertThat(pendingDisplay!!.id).isEqualTo(1)
+        }
+
+    @Test
     fun onPendingDisplay_internalDisplay_ignored() =
         testScope.runTest {
             val pendingDisplay by lastPendingDisplay()
@@ -365,7 +384,7 @@
         testScope.runTest {
             val pendingDisplay by lastPendingDisplay()
 
-            sendOnDisplayConnected(1, Display.TYPE_EXTERNAL)
+            sendOnDisplayConnected(1, TYPE_EXTERNAL)
             sendOnDisplayConnected(2, Display.TYPE_INTERNAL)
 
             assertThat(pendingDisplay!!.id).isEqualTo(1)
@@ -416,7 +435,7 @@
         whenever(displayManager.getDisplay(eq(id))).thenReturn(null)
     }
 
-    private fun sendOnDisplayConnected(id: Int, displayType: Int = Display.TYPE_EXTERNAL) {
+    private fun sendOnDisplayConnected(id: Int, displayType: Int = TYPE_EXTERNAL) {
         val mockDisplay = display(id = id, type = displayType)
         whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay)
         connectedDisplayListener.value.onDisplayConnected(id)
@@ -424,15 +443,25 @@
 
     private fun setDisplays(displays: List<Display>) {
         whenever(displayManager.displays).thenReturn(displays.toTypedArray())
+        displays.forEach { display ->
+            whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display)
+        }
+    }
+
+    private fun setAllDisplaysIncludingDisabled(vararg ids: Int) {
+        val displays = ids.map { display(type = TYPE_EXTERNAL, id = it) }.toTypedArray()
+        whenever(
+                displayManager.getDisplays(
+                    eq(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+                )
+            )
+            .thenReturn(displays)
+        displays.forEach { display ->
+            whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display)
+        }
     }
 
     private fun setDisplays(vararg ids: Int) {
-        setDisplays(ids.map { display(it) })
-    }
-
-    private fun display(id: Int): Display {
-        return mock<Display>().also { mockDisplay ->
-            whenever(mockDisplay.displayId).thenReturn(id)
-        }
+        setDisplays(ids.map { display(type = TYPE_EXTERNAL, id = it) })
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index f62137c..f3c9432 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -172,7 +172,6 @@
         val featureFlags =
             FakeFeatureFlags().apply {
                 set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
-                set(Flags.REVAMPED_WALLPAPER_UI, true)
                 set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
                 set(Flags.FACE_AUTH_REFACTOR, true)
             }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index f78d051..a45b0bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -186,8 +186,6 @@
     private @Mock AuthController mAuthController;
     private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
-    private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
-            mKeyguardUpdateMonitorCallbackCaptor;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
     private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
@@ -294,25 +292,6 @@
     }
 
     @Test
-    @TestableLooper.RunWithLooper(setAsMainLooper = true)
-    public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
-        // GIVEN keyguard is not enabled and isn't showing
-        mViewMediator.onSystemReady();
-        mViewMediator.setKeyguardEnabled(false);
-        TestableLooper.get(this).processAllMessages();
-        captureKeyguardUpdateMonitorCallback();
-        assertFalse(mViewMediator.isShowingAndNotOccluded());
-
-        // WHEN lockdown occurs
-        when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true);
-        mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0);
-
-        // THEN keyguard is shown
-        TestableLooper.get(this).processAllMessages();
-        assertTrue(mViewMediator.isShowingAndNotOccluded());
-    }
-
-    @Test
     public void testOnGoingToSleep_UpdatesKeyguardGoingAway() {
         mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
         verify(mUpdateMonitor).dispatchKeyguardGoingAway(false);
@@ -1120,10 +1099,6 @@
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
     }
 
-    private void captureKeyguardUpdateMonitorCallback() {
-        verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
-    }
-
     private void captureKeyguardStateControllerCallback() {
         verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index d36e778..b9c0b7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -20,6 +20,7 @@
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsResponse
 import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.service.quickaccesswallet.WalletCard
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
@@ -38,6 +39,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -92,6 +94,40 @@
     }
 
     @Test
+    fun affordance_keyguardShowing_hasNonPaymentCard_modelIsNone() =
+        runTest(UnconfinedTestDispatcher()) {
+            setUpState(cardType = WalletCard.CARD_TYPE_NON_PAYMENT)
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+            job.cancel()
+        }
+
+    @Test
+    fun affordance_keyguardShowing_hasPaymentCard_visibleModel() =
+        runTest(UnconfinedTestDispatcher()) {
+            setUpState(cardType = WalletCard.CARD_TYPE_PAYMENT)
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+
+            val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+            assertThat(visibleModel.icon)
+                .isEqualTo(
+                    Icon.Loaded(
+                        drawable = ICON,
+                        contentDescription =
+                            ContentDescription.Resource(
+                                res = R.string.accessibility_wallet_button,
+                            ),
+                    )
+                )
+            job.cancel()
+        }
+
+    @Test
     fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
         setUpState(isWalletFeatureAvailable = false)
         var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
@@ -187,6 +223,7 @@
         isWalletServiceAvailable: Boolean = true,
         isWalletQuerySuccessful: Boolean = true,
         hasSelectedCard: Boolean = true,
+        cardType: Int = WalletCard.CARD_TYPE_UNKNOWN
     ) {
         val walletClient: QuickAccessWalletClient = mock()
         whenever(walletClient.tileIcon).thenReturn(ICON)
@@ -202,7 +239,19 @@
                 if (isWalletQuerySuccessful) {
                     onWalletCardsRetrieved(
                         if (hasSelectedCard) {
-                            GetWalletCardsResponse(listOf(mock()), 0)
+                            GetWalletCardsResponse(
+                                listOf(
+                                    WalletCard.Builder(
+                                            /*cardId= */ CARD_ID,
+                                            /*cardType= */ cardType,
+                                            /*cardImage= */ mock(),
+                                            /*contentDescription=  */ CARD_DESCRIPTION,
+                                            /*pendingIntent= */ mock()
+                                        )
+                                        .build()
+                                ),
+                                0
+                            )
                         } else {
                             GetWalletCardsResponse(emptyList(), 0)
                         }
@@ -216,5 +265,7 @@
 
     companion object {
         private val ICON: Drawable = mock()
+        private const val CARD_ID: String = "Id"
+        private const val CARD_DESCRIPTION: String = "Description"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index a76c885..6b194f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,6 +36,7 @@
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
 import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.R
@@ -285,7 +286,7 @@
             allPreconditionsToRunFaceAuthAreTrue()
 
             FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
             uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
 
@@ -318,12 +319,12 @@
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
             clearInvocations(faceManager)
             clearInvocations(uiEventLogger)
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             verifyNoMoreInteractions(faceManager)
             verifyNoMoreInteractions(uiEventLogger)
         }
@@ -335,7 +336,7 @@
             verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
 
             authenticationCallback.value.onAuthenticationError(
@@ -389,7 +390,7 @@
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
 
             var wasAuthCancelled = false
@@ -443,7 +444,7 @@
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
 
             // Enter cancelling state
@@ -451,7 +452,7 @@
             clearInvocations(faceManager)
 
             // Auth is while cancelling.
-            underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
             // Auth is not started
             verifyNoMoreInteractions(faceManager)
 
@@ -474,14 +475,14 @@
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
 
             clearInvocations(faceManager)
             underTest.cancel()
             advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
         }
 
@@ -492,7 +493,7 @@
             allPreconditionsToRunFaceAuthAreTrue()
             val emittedValues by collectValues(underTest.authenticationStatus)
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             underTest.cancel()
             advanceTimeBy(100)
             underTest.cancel()
@@ -519,7 +520,7 @@
             initCollectors()
             allPreconditionsToRunFaceAuthAreTrue()
 
-            underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
             faceAuthenticateIsCalled()
 
             authenticationCallback.value.onAuthenticationHelp(9, "help msg")
@@ -562,8 +563,26 @@
         }
 
     @Test
-    fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() =
-        testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } }
+    fun authenticateDoesNotRunIfUserSwitchingIsCurrentlyInProgress() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                fakeUserRepository.setSelectedUserInfo(
+                    primaryUser,
+                    SelectionStatus.SELECTION_IN_PROGRESS
+                )
+            }
+        }
+
+    @Test
+    fun detectDoesNotRunIfUserSwitchingIsCurrentlyInProgress() =
+        testScope.runTest {
+            testGatingCheckForDetect {
+                fakeUserRepository.setSelectedUserInfo(
+                    userInfo = primaryUser,
+                    selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+                )
+            }
+        }
 
     @Test
     fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
@@ -582,12 +601,6 @@
         testScope.runTest { testGatingCheckForFaceAuth { underTest.setLockedOut(true) } }
 
     @Test
-    fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) }
-        }
-
-    @Test
     fun authenticateDoesNotRunWhenKeyguardIsGoingAway() =
         testScope.runTest {
             testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) }
@@ -608,14 +621,6 @@
         }
 
     @Test
-    fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() =
-        testScope.runTest {
-            testGatingCheckForFaceAuth {
-                biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
-            }
-        }
-
-    @Test
     fun authenticateDoesNotRunWhenSecureCameraIsActive() =
         testScope.runTest {
             testGatingCheckForFaceAuth {
@@ -672,7 +677,7 @@
             // Flip one precondition to false.
             biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
             assertThat(canFaceAuthRun()).isFalse()
-            underTest.authenticate(
+            underTest.requestAuthenticate(
                 FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
                 fallbackToDetection = true
             )
@@ -693,7 +698,7 @@
 
             trustRepository.setCurrentUserTrusted(true)
             assertThat(canFaceAuthRun()).isFalse()
-            underTest.authenticate(
+            underTest.requestAuthenticate(
                 FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
                 fallbackToDetection = true
             )
@@ -884,10 +889,6 @@
         }
 
     @Test
-    fun detectDoesNotRunWhenUserSwitchingInProgress() =
-        testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } }
-
-    @Test
     fun detectDoesNotRunWhenKeyguardGoingAway() =
         testScope.runTest {
             testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) }
@@ -1075,6 +1076,28 @@
             faceAuthenticateIsCalled()
         }
 
+    @Test
+    fun queuedAuthOnlyRequestShouldNotBeProcessedIfOnlyDetectionCanBeRun() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            // This will prevent auth from running but not detection
+            biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
+
+            runCurrent()
+            assertThat(canFaceAuthRun()).isFalse()
+
+            underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+            runCurrent()
+
+            faceDetectIsNotCalled()
+            faceAuthenticateIsNotCalled()
+
+            biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+            faceAuthenticateIsCalled()
+        }
+
     private suspend fun TestScope.testGatingCheckForFaceAuth(
         gatingCheckModifier: suspend () -> Unit
     ) {
@@ -1087,10 +1110,18 @@
         // gating check doesn't allow face auth to run.
         assertThat(underTest.canRunFaceAuth.value).isFalse()
 
+        // request face auth just before gating conditions become true, this ensures any race
+        // conditions won't prevent face auth from running
+        underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+        faceAuthenticateIsNotCalled()
+
         // flip the gating check back on.
         allPreconditionsToRunFaceAuthAreTrue()
+        assertThat(underTest.canRunFaceAuth.value).isTrue()
 
-        triggerFaceAuth(false)
+        faceAuthenticateIsCalled()
+        assertThat(authRunning()).isTrue()
+        cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
 
         // Flip gating check off
         gatingCheckModifier()
@@ -1101,13 +1132,17 @@
         clearInvocations(faceManager)
 
         // Try auth again
-        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+        underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+        runCurrent()
 
         // Auth can't run again
         faceAuthenticateIsNotCalled()
     }
 
-    private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
+    private suspend fun TestScope.testGatingCheckForDetect(
+        gatingCheckModifier: suspend () -> Unit
+    ) {
         initCollectors()
         allPreconditionsToRunFaceAuthAreTrue()
 
@@ -1118,7 +1153,11 @@
         assertThat(canFaceAuthRun()).isFalse()
 
         // Trigger authenticate with detection fallback
-        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+        underTest.requestAuthenticate(
+            FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+            fallbackToDetection = true
+        )
+        runCurrent()
 
         faceAuthenticateIsNotCalled()
         faceDetectIsCalled()
@@ -1133,15 +1172,21 @@
         clearInvocations(faceManager)
 
         // Try to run detect again
-        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+        underTest.requestAuthenticate(
+            FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+            fallbackToDetection = true
+        )
 
         // Detect won't run because preconditions are not true anymore.
         faceDetectIsNotCalled()
     }
 
-    private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) {
+    private fun TestScope.triggerFaceAuth(fallbackToDetect: Boolean) {
         assertThat(canFaceAuthRun()).isTrue()
-        underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+        underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+
+        runCurrent()
+
         faceAuthenticateIsCalled()
         assertThat(authRunning()).isTrue()
         cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
@@ -1150,7 +1195,6 @@
     private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
         verify(faceManager, atLeastOnce())
             .addLockoutResetCallback(faceLockoutResetCallback.capture())
-        underTest.resumeFaceAuth()
         trustRepository.setCurrentUserTrusted(false)
         keyguardRepository.setKeyguardGoingAway(false)
         keyguardRepository.setWakefulnessModel(
@@ -1164,7 +1208,7 @@
         biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
         biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
         biometricSettingsRepository.setIsUserInLockdown(false)
-        fakeUserRepository.setSelectedUserInfo(primaryUser)
+        fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
         faceLockoutResetCallback.value.onLockoutReset(0)
         bouncerRepository.setAlternateVisible(true)
         keyguardRepository.setKeyguardShowing(true)
@@ -1187,7 +1231,9 @@
 
     private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
 
-    private fun faceDetectIsCalled() {
+    private fun TestScope.faceDetectIsCalled() {
+        runCurrent()
+
         verify(faceManager)
             .detectFace(
                 cancellationSignal.capture(),
@@ -1196,7 +1242,9 @@
             )
     }
 
-    private fun faceAuthenticateIsCalled() {
+    private fun TestScope.faceAuthenticateIsCalled() {
+        runCurrent()
+
         verify(faceManager)
             .authenticate(
                 isNull(),
@@ -1207,7 +1255,9 @@
             )
     }
 
-    private fun faceAuthenticateIsNotCalled() {
+    private fun TestScope.faceAuthenticateIsNotCalled() {
+        runCurrent()
+
         verify(faceManager, never())
             .authenticate(
                 isNull(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index da70a9f..2ed9de2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -224,23 +224,7 @@
         }
 
     @Test
-    fun faceAuthIsPausedWhenUserSwitchingIsInProgress() =
-        testScope.runTest {
-            underTest.start()
-
-            fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
-            runCurrent()
-            fakeUserRepository.setSelectedUserInfo(
-                secondaryUser,
-                SelectionStatus.SELECTION_IN_PROGRESS
-            )
-            runCurrent()
-
-            assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
-        }
-
-    @Test
-    fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() =
+    fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() =
         testScope.runTest {
             underTest.start()
 
@@ -251,7 +235,6 @@
                 SelectionStatus.SELECTION_IN_PROGRESS
             )
             runCurrent()
-            assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
 
             bouncerRepository.setPrimaryShow(true)
             // New user is not locked out.
@@ -262,7 +245,6 @@
             )
             runCurrent()
 
-            assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse()
             assertThat(faceAuthRepository.isLockedOut.value).isFalse()
 
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 0050d64..0bbeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -302,7 +302,6 @@
                 featureFlags =
                     FakeFeatureFlags().apply {
                         set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
-                        set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
                         set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
                     },
                 broadcastDispatcher = fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 681fce8..d6b621e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.util.mockito.whenever
@@ -60,6 +61,7 @@
     private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
     @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
     @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
+    @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
     @Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection
     @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
     @Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
@@ -78,6 +80,7 @@
                 defaultAmbientIndicationAreaSection,
                 defaultSettingsPopupMenuSection,
                 defaultStatusViewSection,
+                defaultStatusBarViewSection,
                 defaultNSSLSection,
                 splitShadeGuidelines,
                 aodNotificationIconsSection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index b30dc9c..f40ccfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -44,6 +44,7 @@
 
     private val underTest =
         LockscreenSceneViewModel(
+            applicationScope = testScope.backgroundScope,
             authenticationInteractor = authenticationInteractor,
             longPress =
                 KeyguardLongPressViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 1536c17..b50032f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -73,6 +73,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -705,12 +706,12 @@
     fun updateNoteTaskAsUser_sameUser_shouldUpdateShortcuts() {
         val user = UserHandle.CURRENT
         val controller = spy(createNoteTaskController())
-        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+        doNothing().whenever(controller).launchUpdateNoteTaskAsUser(any())
         whenever(controller.getCurrentRunningUser()).thenReturn(user)
 
         controller.updateNoteTaskAsUser(user)
 
-        verify(controller).updateNoteTaskAsUserInternal(user)
+        verify(controller).launchUpdateNoteTaskAsUser(user)
         verify(context, never()).startServiceAsUser(any(), any())
     }
 
@@ -718,12 +719,12 @@
     fun updateNoteTaskAsUser_differentUser_shouldUpdateShortcutsInUserProcess() {
         val user = UserHandle.CURRENT
         val controller = spy(createNoteTaskController(isEnabled = true))
-        doNothing().whenever(controller).updateNoteTaskAsUserInternal(any())
+        doNothing().whenever(controller).launchUpdateNoteTaskAsUser(any())
         whenever(controller.getCurrentRunningUser()).thenReturn(UserHandle.SYSTEM)
 
         controller.updateNoteTaskAsUser(user)
 
-        verify(controller, never()).updateNoteTaskAsUserInternal(any())
+        verify(controller, never()).launchUpdateNoteTaskAsUser(any())
         val intent = withArgCaptor { verify(context).startServiceAsUser(capture(), eq(user)) }
         assertThat(intent).hasComponentClass(NoteTaskControllerUpdateService::class.java)
     }
@@ -733,7 +734,8 @@
     @Test
     fun updateNoteTaskAsUserInternal_withNotesRole_withShortcuts_shouldUpdateShortcuts() {
         createNoteTaskController(isEnabled = true)
-            .updateNoteTaskAsUserInternal(userTracker.userHandle)
+            .launchUpdateNoteTaskAsUser(userTracker.userHandle)
+        testScope.runCurrent()
 
         val actualComponent = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -768,7 +770,8 @@
             .thenReturn(emptyList())
 
         createNoteTaskController(isEnabled = true)
-            .updateNoteTaskAsUserInternal(userTracker.userHandle)
+            .launchUpdateNoteTaskAsUser(userTracker.userHandle)
+        testScope.runCurrent()
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
@@ -787,7 +790,8 @@
     @Test
     fun updateNoteTaskAsUserInternal_flagDisabled_shouldDisableShortcuts() {
         createNoteTaskController(isEnabled = false)
-            .updateNoteTaskAsUserInternal(userTracker.userHandle)
+            .launchUpdateNoteTaskAsUser(userTracker.userHandle)
+        testScope.runCurrent()
 
         val argument = argumentCaptor<ComponentName>()
         verify(context.packageManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 95bb3e0..162b7b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -22,6 +22,8 @@
 import android.testing.AndroidTestingRunner
 import android.view.KeyEvent
 import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
+import android.view.KeyEvent.KEYCODE_N
 import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -30,7 +32,6 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -43,7 +44,6 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
@@ -66,6 +66,7 @@
 
     private val executor = FakeExecutor(FakeSystemClock())
     private val userTracker = FakeUserTracker()
+    private val handlerCallbacks = mutableListOf<Runnable>()
 
     @Before
     fun setUp() {
@@ -74,19 +75,27 @@
     }
 
     private fun createUnderTest(
-            isEnabled: Boolean,
-            bubbles: Bubbles?,
+        isEnabled: Boolean,
+        bubbles: Bubbles?,
     ): NoteTaskInitializer =
-            NoteTaskInitializer(
-                    controller = controller,
-                    commandQueue = commandQueue,
-                    optionalBubbles = Optional.ofNullable(bubbles),
-                    isEnabled = isEnabled,
-                    roleManager = roleManager,
-                    userTracker = userTracker,
-                    keyguardUpdateMonitor = keyguardMonitor,
-                    backgroundExecutor = executor,
-            )
+        NoteTaskInitializer(
+            controller = controller,
+            commandQueue = commandQueue,
+            optionalBubbles = Optional.ofNullable(bubbles),
+            isEnabled = isEnabled,
+            roleManager = roleManager,
+            userTracker = userTracker,
+            keyguardUpdateMonitor = keyguardMonitor,
+            backgroundExecutor = executor,
+        )
+
+    private fun createKeyEvent(
+        action: Int,
+        code: Int,
+        downTime: Long = 0L,
+        eventTime: Long = 0L,
+        metaState: Int = 0
+    ): KeyEvent = KeyEvent(downTime, eventTime, action, code, 0 /*repeat*/, metaState)
 
     @Test
     fun initialize_withUserUnlocked() {
@@ -120,12 +129,12 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-                commandQueue,
-                bubbles,
-                controller,
-                roleManager,
-                userManager,
-                keyguardMonitor,
+            commandQueue,
+            bubbles,
+            controller,
+            roleManager,
+            userManager,
+            keyguardMonitor,
         )
     }
 
@@ -136,18 +145,23 @@
         underTest.initialize()
 
         verifyZeroInteractions(
-                commandQueue,
-                bubbles,
-                controller,
-                roleManager,
-                userManager,
-                keyguardMonitor,
+            commandQueue,
+            bubbles,
+            controller,
+            roleManager,
+            userManager,
+            keyguardMonitor,
         )
     }
 
     @Test
     fun initialize_handleSystemKey() {
-        val expectedKeyEvent = KeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL)
+        val expectedKeyEvent =
+            createKeyEvent(
+                ACTION_DOWN,
+                KEYCODE_N,
+                metaState = KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON
+            )
         val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
         underTest.initialize()
         val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }
@@ -176,7 +190,7 @@
         underTest.initialize()
         val callback = withArgCaptor {
             verify(roleManager)
-                    .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
+                .addOnRoleHoldersChangedListenerAsUser(any(), capture(), eq(UserHandle.ALL))
         }
 
         callback.onRoleHoldersChanged(ROLE_NOTES, userTracker.userHandle)
@@ -203,4 +217,22 @@
 
         verify(controller, times(2)).updateNoteTaskForCurrentUserAndManagedProfiles()
     }
+
+    @Test
+    fun tailButtonGestureDetection_singlePress_shouldShowNoteTaskOnUp() {
+        val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+        underTest.initialize()
+        val callback = withArgCaptor { verify(commandQueue).addCallback(capture()) }
+
+        callback.handleSystemKey(
+            createKeyEvent(ACTION_DOWN, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 0)
+        )
+        verify(controller, never()).showNoteTask(any())
+
+        callback.handleSystemKey(
+            createKeyEvent(ACTION_UP, KEYCODE_STYLUS_BUTTON_TAIL, downTime = 0, eventTime = 50)
+        )
+
+        verify(controller).showNoteTask(any())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index b00ae39..dd55bad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -395,7 +395,7 @@
                 PendingIntent.getActivity(mContext, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
         WalletCard walletCard =
                 new WalletCard.Builder(
-                    CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build();
+                        CARD_ID, mCardImage, CARD_DESCRIPTION, pendingIntent).build();
         GetWalletCardsResponse response =
                 new GetWalletCardsResponse(Collections.singletonList(walletCard), 0);
 
@@ -410,6 +410,22 @@
     }
 
     @Test
+    public void testQueryCards_cardDataPayment_updateSideViewDrawable() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+        setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_PAYMENT);
+
+        assertNotNull(mTile.getState().sideViewCustomDrawable);
+    }
+
+    @Test
+    public void testQueryCards_cardDataNonPayment_updateSideViewDrawable() {
+        when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+        setUpWalletCardWithType(/* hasCard =*/ true, WalletCard.CARD_TYPE_NON_PAYMENT);
+
+        assertNull(mTile.getState().sideViewCustomDrawable);
+    }
+
+    @Test
     public void testQueryCards_noCards_notUpdateSideViewDrawable() {
         setUpWalletCard(/* hasCard= */ false);
 
@@ -438,6 +454,29 @@
         verifyZeroInteractions(mQuickAccessWalletClient);
     }
 
+    private WalletCard createWalletCardWithType(Context context, int cardType) {
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE);
+        return new WalletCard.Builder(CARD_ID, cardType, CARD_IMAGE, CARD_DESCRIPTION,
+                pendingIntent).build();
+    }
+
+    private void setUpWalletCardWithType(boolean hasCard, int cardType) {
+        GetWalletCardsResponse response =
+                new GetWalletCardsResponse(
+                        hasCard
+                                ? Collections.singletonList(
+                                createWalletCardWithType(mContext, cardType))
+                                : Collections.EMPTY_LIST, 0);
+
+        mTile.handleSetListening(true);
+
+        verify(mController).queryWalletCards(mCallbackCaptor.capture());
+
+        mCallbackCaptor.getValue().onWalletCardsRetrieved(response);
+        mTestableLooper.processAllMessages();
+    }
+
     private void setUpWalletCard(boolean hasCard) {
         GetWalletCardsResponse response =
                 new GetWalletCardsResponse(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
new file mode 100644
index 0000000..47b4244
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
@@ -0,0 +1,41 @@
+package com.android.systemui.qs.tiles.base
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+
+    @Mock private lateinit var activityStarted: ActivityStarter
+
+    lateinit var underTest: QSTileIntentUserActionHandler
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = QSTileIntentUserActionHandler(activityStarted)
+    }
+
+    @Test
+    fun testPassesIntentToStarter() {
+        val intent = Intent("test.ACTION")
+
+        underTest.handle(QSTileUserAction.Click(context, null), intent)
+
+        verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
new file mode 100644
index 0000000..643866e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -0,0 +1,90 @@
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.MediumTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// TODO(b/299909368): Add more tests
+@MediumTest
+@RoboPilotTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
+
+    private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
+    private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
+
+    private val testCoroutineDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testCoroutineDispatcher)
+
+    private lateinit var underTest: QSTileViewModel
+
+    @Before
+    fun setup() {
+        underTest = createViewModel(testScope)
+    }
+
+    @Test
+    fun testDoesntListenStateUntilCreated() =
+        testScope.runTest {
+            assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
+
+            underTest.onLifecycle(QSTileLifecycle.ALIVE)
+            underTest.onUserIdChanged(1)
+
+            assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
+
+            underTest.state.launchIn(backgroundScope)
+            runCurrent()
+
+            assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
+            assertThat(fakeQSTileDataInteractor.dataRequests.first())
+                .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+        }
+
+    private fun createViewModel(
+        scope: TestScope,
+        config: QSTileConfig = TEST_QS_TILE_CONFIG,
+    ): QSTileViewModel =
+        object :
+            BaseQSTileViewModel<Any>(
+                config,
+                fakeQSTileUserActionInteractor,
+                fakeQSTileDataInteractor,
+                object : QSTileDataToStateMapper<Any> {
+                    override fun map(config: QSTileConfig, data: Any): QSTileState {
+                        return QSTileState(config.tileIcon, config.tileLabel)
+                    }
+                },
+                testCoroutineDispatcher,
+                tileScope = scope.backgroundScope,
+            ) {}
+
+    private companion object {
+        val TEST_QS_TILE_CONFIG =
+            QSTileConfig(
+                TileSpec.create("default"),
+                Icon.createWithContentUri(""),
+                "",
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index e353a53..18fa0be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -96,6 +96,7 @@
     @Mock private lateinit var navBarController: NavigationBarController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
     @Mock private lateinit var shadeViewController: ShadeViewController
+    @Mock private lateinit var screenPinningRequest: ScreenPinningRequest
     @Mock private lateinit var navModeController: NavigationModeController
     @Mock private lateinit var statusBarWinController: NotificationShadeWindowController
     @Mock private lateinit var userTracker: UserTracker
@@ -136,6 +137,7 @@
                 { navBarController },
                 { Optional.of(centralSurfaces) },
                 { shadeViewController },
+                screenPinningRequest,
                 navModeController,
                 statusBarWinController,
                 sysUiState,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 141fcbb..78385cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -123,6 +123,7 @@
 
     private val lockscreenSceneViewModel =
         LockscreenSceneViewModel(
+            applicationScope = testScope.backgroundScope,
             authenticationInteractor = authenticationInteractor,
             longPress =
                 KeyguardLongPressViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index 17ee3a1..f6195aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -78,8 +78,8 @@
         @JvmStatic
         fun testCases() = buildList {
             repeat(4) { combination ->
-                val isComposeAvailable = combination and 0b100 != 0
-                val areAllFlagsSet = combination and 0b001 != 0
+                val isComposeAvailable = combination and 0b10 != 0
+                val areAllFlagsSet = combination and 0b01 != 0
 
                 val expectedEnabled = isComposeAvailable && areAllFlagsSet
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index cfdf66e..a105c15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -180,7 +180,7 @@
         }
 
     @Test
-    fun executeScreenshots_doesNotReportFinishedIfOneFinishesOtherFails() =
+    fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
             val onSaved = { _: Uri -> }
@@ -207,7 +207,7 @@
         }
 
     @Test
-    fun executeScreenshots_doesNotReportFinishedAfterOneFails() =
+    fun executeScreenshots_allDisplaysFail_reportsFail() =
         testScope.runTest {
             setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
             val onSaved = { _: Uri -> }
@@ -224,11 +224,12 @@
             capturer0.value.reportError()
 
             verify(callback, never()).onFinish()
-            verify(callback).reportError()
+            verify(callback, never()).reportError()
 
-            capturer1.value.onFinish()
+            capturer1.value.reportError()
 
             verify(callback, never()).onFinish()
+            verify(callback).reportError()
             screenshotExecutor.onDestroy()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 13bf53b..9130bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -120,6 +120,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.shade.transition.ShadeTransitionController;
@@ -321,7 +322,6 @@
             mEmptySpaceClickListenerCaptor;
     @Mock protected ActivityStarter mActivityStarter;
     @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
-    @Mock protected ShadeRepository mShadeRepository;
     @Mock private ShadeInteractor mShadeInteractor;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
@@ -342,6 +342,7 @@
     protected Handler mMainHandler;
     protected View.OnLayoutChangeListener mLayoutChangeListener;
     protected KeyguardStatusViewController mKeyguardStatusViewController;
+    protected ShadeRepository mShadeRepository;
 
     protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
     protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
@@ -363,6 +364,7 @@
         mFakeKeyguardRepository = keyguardInteractorDeps.getRepository();
         mKeyguardBottomAreaInteractor = new KeyguardBottomAreaInteractor(mFakeKeyguardRepository);
         mKeyguardInteractor = keyguardInteractorDeps.getKeyguardInteractor();
+        mShadeRepository = new FakeShadeRepository();
 
         SystemClock systemClock = new FakeSystemClock();
         mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 638c266..6dadd4c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -197,6 +197,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() {
+        enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
                 /* lockIconPadding= */ 20,
                 /* indicationPadding= */ 0,
@@ -209,6 +210,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() {
+        enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
                 /* lockIconPadding= */ 0,
                 /* indicationPadding= */ 30,
@@ -221,6 +223,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() {
+        enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
                 /* lockIconPadding= */ 0,
                 /* indicationPadding= */ 0,
@@ -233,6 +236,7 @@
 
     @Test
     public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() {
+        enableSplitShade(/* enabled= */ false);
         setBottomPadding(/* stackScrollLayoutBottom= */ 100,
                 /* lockIconPadding= */ 10,
                 /* indicationPadding= */ 8,
@@ -244,6 +248,19 @@
     }
 
     @Test
+    public void getVerticalSpaceForLockscreenShelf_splitShade() {
+        enableSplitShade(/* enabled= */ true);
+        setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+                /* lockIconPadding= */ 10,
+                /* indicationPadding= */ 8,
+                /* ambientPadding= */ 0);
+
+        when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
+        assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
+                .isEqualTo(0);
+    }
+
+    @Test
     public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
         mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
                 mNotificationPanelViewController.getMaxPanelHeight() / 2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 8f8b840..7d5f68e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -130,7 +130,7 @@
     var viewVisibility = View.GONE
     var viewAlpha = 1f
 
-    private val systemIcons = LinearLayout(context)
+    private val systemIconsHoverContainer = LinearLayout(context)
     private lateinit var shadeHeaderController: ShadeHeaderController
     private lateinit var carrierIconSlots: List<String>
     private val configurationController = FakeConfigurationController()
@@ -150,7 +150,8 @@
             .thenReturn(batteryMeterView)
 
         whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
-        whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
+        whenever<View>(view.requireViewById(R.id.hover_system_icons_container))
+            .thenReturn(systemIconsHoverContainer)
 
         viewContext = Mockito.spy(context)
         whenever(view.context).thenReturn(viewContext)
@@ -457,12 +458,12 @@
     }
 
     @Test
-    fun testLargeScreenActive_collapseActionRun_onSystemIconsClick() {
+    fun testLargeScreenActive_collapseActionRun_onSystemIconsHoverContainerClick() {
         shadeHeaderController.largeScreenActive = true
         var wasRun = false
         shadeHeaderController.shadeCollapseAction = Runnable { wasRun = true }
 
-        systemIcons.performClick()
+        systemIconsHoverContainer.performClick()
 
         assertThat(wasRun).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 9275ccb..d4b69fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -402,6 +402,7 @@
             assertThat(actual).isEqualTo(0.8f)
         }
 
+    @Test
     fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
         testScope.runTest {
             val actual by collectLastValue(underTest.shadeExpansion)
@@ -410,27 +411,31 @@
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
             overrideResource(R.bool.config_use_split_notification_shade, true)
             configurationRepository.onAnyConfigurationChange()
-            runCurrent()
             shadeRepository.setQsExpansion(.5f)
             shadeRepository.setLegacyShadeExpansion(.7f)
+            runCurrent()
 
             // THEN legacy shade expansion is passed through
             assertThat(actual).isEqualTo(.7f)
         }
 
+    @Test
     fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
         testScope.runTest {
             val actual by collectLastValue(underTest.shadeExpansion)
 
             // WHEN split shade is not enabled and QS is expanded
             keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, false)
             shadeRepository.setQsExpansion(.5f)
             shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
 
             // THEN shade expansion is zero
             assertThat(actual).isEqualTo(0f)
         }
 
+    @Test
     fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
         testScope.runTest {
             val actual by collectLastValue(underTest.shadeExpansion)
@@ -471,7 +476,7 @@
     @Test
     fun expanding_shadeDraggedDown_expandingTrue() =
         testScope.runTest() {
-            val actual by collectLastValue(underTest.anyExpanding)
+            val actual by collectLastValue(underTest.isAnyExpanding)
 
             // GIVEN shade and QS collapsed
             shadeRepository.setLegacyShadeExpansion(0f)
@@ -489,7 +494,7 @@
     @Test
     fun expanding_qsDraggedDown_expandingTrue() =
         testScope.runTest() {
-            val actual by collectLastValue(underTest.anyExpanding)
+            val actual by collectLastValue(underTest.isAnyExpanding)
 
             // GIVEN shade and QS collapsed
             shadeRepository.setLegacyShadeExpansion(0f)
@@ -507,7 +512,7 @@
     @Test
     fun expanding_shadeDraggedUpAndDown() =
         testScope.runTest() {
-            val actual by collectLastValue(underTest.anyExpanding)
+            val actual by collectLastValue(underTest.isAnyExpanding)
 
             // WHEN shade starts collapsed then partially expanded
             shadeRepository.setLegacyShadeExpansion(0f)
@@ -532,7 +537,7 @@
             // THEN anyExpanding is still true
             assertThat(actual).isTrue()
 
-            // WHEN shade fully shadeExpanded
+            // WHEN shade fully expanded
             shadeRepository.setLegacyShadeExpansion(1f)
             runCurrent()
 
@@ -550,7 +555,7 @@
     @Test
     fun expanding_shadeDraggedDownThenUp_expandingFalse() =
         testScope.runTest() {
-            val actual by collectLastValue(underTest.anyExpanding)
+            val actual by collectLastValue(underTest.isAnyExpanding)
 
             // GIVEN shade starts collapsed
             shadeRepository.setLegacyShadeExpansion(0f)
@@ -708,4 +713,227 @@
             // THEN expansion is still 0
             assertThat(expansionAmount).isEqualTo(0f)
         }
+
+    @Test
+    fun userInteractingWithShade_shadeDraggedUpAndDown() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed but tracking is not stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged halfway and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(.6f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade completes expansion stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeExpanded() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged down halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully expanded and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadePartiallyExpanded() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade collapsed and not tracking input
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade partially expanded
+            shadeRepository.setLegacyShadeExpansion(.4f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN tracking is stopped
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade goes back to collapsed
+            shadeRepository.setLegacyShadeExpansion(0f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithShade_shadeCollapsed() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithShade)
+            // GIVEN shade expanded and not tracking input
+            shadeRepository.setLegacyShadeExpansion(1f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN shade tracking starts
+            shadeRepository.setLegacyShadeTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade dragged up halfway
+            shadeRepository.setLegacyShadeExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN shade fully collapsed and tracking is stopped
+            shadeRepository.setLegacyShadeExpansion(0f)
+            shadeRepository.setLegacyShadeTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
+
+    @Test
+    fun userInteractingWithQs_qsDraggedUpAndDown() =
+        testScope.runTest() {
+            val actual by collectLastValue(underTest.isUserInteractingWithQs)
+            // GIVEN qs collapsed and not tracking input
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+
+            // WHEN qs tracking starts
+            shadeRepository.setLegacyQsTracking(true)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged down halfway
+            shadeRepository.setQsExpansion(.5f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully expanded but tracking is not stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs fully collapsed but tracking is not stopped
+            shadeRepository.setQsExpansion(0f)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs dragged halfway and tracking is stopped
+            shadeRepository.setQsExpansion(.6f)
+            shadeRepository.setLegacyQsTracking(false)
+            runCurrent()
+
+            // THEN user is interacting
+            assertThat(actual).isTrue()
+
+            // WHEN qs completes expansion stopped
+            shadeRepository.setQsExpansion(1f)
+            runCurrent()
+
+            // THEN user is not interacting
+            assertThat(actual).isFalse()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index e086712..19d59fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -151,6 +151,24 @@
         }
 
     @Test
+    fun updateLegacyShadeTracking() =
+        testScope.runTest {
+            assertThat(underTest.legacyShadeTracking.value).isEqualTo(false)
+
+            underTest.setLegacyShadeTracking(true)
+            assertThat(underTest.legacyShadeTracking.value).isEqualTo(true)
+        }
+
+    @Test
+    fun updateLegacyQsTracking() =
+        testScope.runTest {
+            assertThat(underTest.legacyQsTracking.value).isEqualTo(false)
+
+            underTest.setLegacyQsTracking(true)
+            assertThat(underTest.legacyQsTracking.value).isEqualTo(true)
+        }
+
+    @Test
     fun updateUdfpsTransitionToFullShadeProgress() =
         testScope.runTest {
             assertThat(underTest.udfpsTransitionToFullShadeProgress.value).isEqualTo(0f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
new file mode 100644
index 0000000..c650e01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2023 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.row
+
+import android.graphics.BitmapFactory
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.NotificationDrawableConsumer
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+private const val FREE_IMAGE_DELAY_MS = 4000L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BigPictureIconManagerTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val testableResources = context.orCreateTestableResources
+    private val imageLoader: ImageLoader = ImageLoader(context, testDispatcher)
+    private var mockConsumer: NotificationDrawableConsumer = mock()
+    private val drawableCaptor = argumentCaptor<Drawable>()
+
+    private lateinit var iconManager: BigPictureIconManager
+
+    private val expectedDrawable by lazy {
+        context.resources.getDrawable(R.drawable.dessert_zombiegingerbread, context.theme)
+    }
+    private val supportedIcon by lazy {
+        Icon.createWithContentUri(
+            Uri.parse(
+                "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+            )
+        )
+    }
+    private val unsupportedIcon by lazy {
+        Icon.createWithBitmap(
+            BitmapFactory.decodeResource(context.resources, R.drawable.dessert_zombiegingerbread)
+        )
+    }
+    private val invalidIcon by lazy { Icon.createWithContentUri(Uri.parse("this.is/broken")) }
+
+    @Before
+    fun setUp() {
+        iconManager =
+            BigPictureIconManager(
+                context,
+                imageLoader,
+                scope = testScope,
+                mainDispatcher = testDispatcher,
+                bgDispatcher = testDispatcher
+            )
+    }
+
+    @Test
+    fun onIconUpdated_supportedType_placeholderLoaded() =
+        testScope.runTest {
+            // WHEN update with a supported icon
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+            // THEN consumer is updated with a placeholder
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsPlaceHolder(drawableCaptor.value)
+            assertSize(drawableCaptor.value)
+        }
+
+    @Test
+    fun onIconUpdated_notSupportedType_fullImageLoaded() =
+        testScope.runTest {
+            // WHEN update with an unsupported icon
+            iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+
+            // THEN consumer is updated with the full image
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsFullImage(drawableCaptor.value)
+            assertSize(drawableCaptor.value)
+        }
+
+    @Test
+    fun onIconUpdated_invalidIcon_drawableIsNull() =
+        testScope.runTest {
+            // WHEN update with an invalid icon
+            iconManager.updateIcon(mockConsumer, invalidIcon).run()
+
+            // THEN consumer is updated with null
+            verify(mockConsumer).setImageDrawable(null)
+        }
+
+    @Test
+    fun onIconUpdated_consumerAlreadySet_nothingHappens() =
+        testScope.runTest {
+            // GIVEN a consumer is set
+            val otherConsumer: NotificationDrawableConsumer = mock()
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            reset(mockConsumer)
+
+            // WHEN a new consumer is set
+            iconManager.updateIcon(otherConsumer, unsupportedIcon).run()
+
+            // THEN nothing happens
+            verifyZeroInteractions(mockConsumer, otherConsumer)
+        }
+
+    @Test
+    fun onIconUpdated_iconAlreadySet_loadsNewIcon() =
+        testScope.runTest {
+            // GIVEN an icon is set
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            reset(mockConsumer)
+
+            // WHEN a new icon is set
+            iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+
+            // THEN consumer is updated with the new image
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsFullImage(drawableCaptor.value)
+            assertSize(drawableCaptor.value)
+        }
+
+    @Test
+    fun onIconUpdated_supportedTypeButTooWide_resizedPlaceholderLoaded() =
+        testScope.runTest {
+            // GIVEN the max width is smaller than our image
+            testableResources.addOverride(
+                com.android.internal.R.dimen.notification_big_picture_max_width,
+                20
+            )
+            iconManager.updateMaxImageSizes()
+
+            // WHEN update with a supported icon
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+            // THEN consumer is updated with the resized placeholder
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsPlaceHolder(drawableCaptor.value)
+            assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20)
+        }
+
+    @Test
+    fun onIconUpdated_supportedTypeButTooHigh_resizedPlaceholderLoaded() =
+        testScope.runTest {
+            // GIVEN the max height is smaller than our image
+            testableResources.addOverride(
+                com.android.internal.R.dimen.notification_big_picture_max_height,
+                20
+            )
+            iconManager.updateMaxImageSizes()
+
+            // WHEN update with a supported icon
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+            // THEN consumer is updated with the resized placeholder
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsPlaceHolder(drawableCaptor.value)
+            assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20)
+        }
+
+    @Test
+    fun onViewShown_placeholderShowing_fullImageLoaded() =
+        testScope.runTest {
+            // GIVEN placeholder is showing
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            reset(mockConsumer)
+
+            // WHEN the view is shown
+            iconManager.onViewShown(true)
+            runCurrent()
+
+            // THEN full image is set
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsFullImage(drawableCaptor.value)
+            assertSize(drawableCaptor.value)
+        }
+
+    @Test
+    fun onViewHidden_fullImageShowing_placeHolderSet() =
+        testScope.runTest {
+            // GIVEN full image is showing and the view is shown
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            iconManager.onViewShown(true)
+            runCurrent()
+            reset(mockConsumer)
+
+            // WHEN the view goes off the screen
+            iconManager.onViewShown(false)
+            // AND we wait a bit
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+
+            // THEN placeholder is set
+            verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+            assertIsPlaceHolder(drawableCaptor.value)
+            assertSize(drawableCaptor.value)
+        }
+
+    @Test
+    fun onViewShownToggled_viewShown_nothingHappens() =
+        testScope.runTest {
+            // GIVEN full image is showing and the view is shown
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            iconManager.onViewShown(true)
+            runCurrent()
+            reset(mockConsumer)
+
+            // WHEN the onViewShown is toggled
+            iconManager.onViewShown(false)
+            runCurrent()
+            iconManager.onViewShown(true)
+            // AND we wait a bit
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+
+            // THEN nothing happens
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    // nice to have tests
+
+    @Test
+    fun onViewShown_fullImageLoaded_nothingHappens() =
+        testScope.runTest {
+            // GIVEN full image is showing
+            iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+            reset(mockConsumer)
+
+            // WHEN the view is shown
+            iconManager.onViewShown(true)
+            runCurrent()
+
+            // THEN nothing happens
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    @Test
+    fun onViewHidden_placeholderShowing_nothingHappens() =
+        testScope.runTest {
+            // GIVEN placeholder image is showing
+            iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+            reset(mockConsumer)
+
+            // WHEN the view is hidden
+            iconManager.onViewShown(false)
+            // AND we wait a bit
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+
+            // THEN nothing happens
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    @Test
+    fun onViewShown_alreadyShowing_nothingHappens() =
+        testScope.runTest {
+            // GIVEN full image is showing and the view is shown
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            iconManager.onViewShown(true)
+            runCurrent()
+            reset(mockConsumer)
+
+            // WHEN view shown called again
+            iconManager.onViewShown(true)
+            runCurrent()
+
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    @Test
+    fun onViewHidden_alreadyHidden_nothingHappens() =
+        testScope.runTest {
+            // GIVEN placeholder image is showing and the view is hidden
+            iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+            iconManager.onViewShown(false)
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+            reset(mockConsumer)
+
+            // WHEN the view is hidden again
+            iconManager.onViewShown(false)
+            // AND we wait a bit
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+
+            // THEN nothing happens
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    @Test
+    fun cancelJobs_freeImageJobRunning_jobCancelled() =
+        testScope.runTest {
+            // GIVEN full image is showing
+            iconManager.updateIcon(mockConsumer, supportedIcon).run()
+            iconManager.onViewShown(true)
+            runCurrent()
+            reset(mockConsumer)
+            // AND the view has just gone off the screen
+            iconManager.onViewShown(false)
+
+            // WHEN cancelJobs is called
+            iconManager.cancelJobs()
+            // AND we wait a bit
+            advanceTimeBy(FREE_IMAGE_DELAY_MS)
+            runCurrent()
+
+            // THEN no more updates are happening
+            verifyZeroInteractions(mockConsumer)
+        }
+
+    private fun assertIsPlaceHolder(drawable: Drawable) {
+        assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java)
+    }
+
+    private fun assertIsFullImage(drawable: Drawable) {
+        assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+    }
+
+    private fun assertSize(
+        drawable: Drawable,
+        expectedWidth: Int = expectedDrawable.intrinsicWidth,
+        expectedHeight: Int = expectedDrawable.intrinsicHeight
+    ) {
+        assertThat(drawable.intrinsicWidth).isEqualTo(expectedWidth)
+        assertThat(drawable.intrinsicHeight).isEqualTo(expectedHeight)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index b3f5ea2..5606216 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -180,7 +180,7 @@
         allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, false);
+        mFeatureFlags.set(Flags.USE_REPOS_FOR_BOUNCER_SHOWING, true);
 
         when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
         when(mKeyguardTransitionRepo.getTransitions()).thenReturn(emptyFlow());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3ad3c15..c71c0c57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -50,6 +50,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.CameraLauncher;
 import com.android.systemui.shade.QuickSettingsController;
@@ -79,6 +80,7 @@
 public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
 
     @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private ShadeController mShadeController;
     @Mock private CommandQueue mCommandQueue;
     @Mock private QuickSettingsController mQuickSettingsController;
@@ -116,6 +118,7 @@
                 mQuickSettingsController,
                 mContext,
                 mContext.getResources(),
+                mScreenPinningRequest,
                 mShadeController,
                 mCommandQueue,
                 mShadeViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index bd3fb9f..6b944ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -116,7 +116,6 @@
 import com.android.systemui.plugins.PluginManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.recents.ScreenPinningRequest;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -285,7 +284,6 @@
     @Mock private PluginManager mPluginManager;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
     @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
-    @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private PluginDependencyProvider mPluginDependencyProvider;
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@@ -508,7 +506,6 @@
                 mDozeServiceHost,
                 mBackActionInteractor,
                 mPowerManager,
-                mScreenPinningRequest,
                 mDozeScrimController,
                 mVolumeComponent,
                 mCommandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index d100c687..79f8dbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -30,6 +30,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -51,6 +53,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeViewStateProvider;
 import com.android.systemui.statusbar.CommandQueue;
@@ -79,6 +83,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
+    private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
     @Mock
     private CarrierTextController mCarrierTextController;
     @Mock
@@ -131,6 +136,7 @@
 
     @Before
     public void setup() throws Exception {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
         mShadeViewStateProvider = new TestShadeViewStateProvider();
 
         MockitoAnnotations.initMocks(this);
@@ -166,6 +172,7 @@
                 mBiometricUnlockController,
                 mStatusBarStateController,
                 mStatusBarContentInsetsProvider,
+                mFeatureFlags,
                 mUserManager,
                 mStatusBarUserChipViewModel,
                 mSecureSettings,
@@ -228,6 +235,30 @@
     }
 
     @Test
+    public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
+        mController.onViewAttached();
+        mController.onViewDetached();
+        reset(mStatusBarIconController);
+
+        mController.onViewAttached();
+
+        verify(mStatusBarIconController, never()).addIconGroup(any());
+    }
+
+    @Test
+    public void onViewReAttached_flagOn_iconManagerReRegistered() {
+        mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+        mController.onViewAttached();
+        mController.onViewDetached();
+        reset(mStatusBarIconController);
+
+        mController.onViewAttached();
+
+        verify(mStatusBarIconController).addIconGroup(any());
+    }
+    
+    @Test
     public void setBatteryListening_true_callbackAdded() {
         mController.setBatteryListening(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ba4e8d3..bac8579 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -976,4 +976,23 @@
         verify(mKeyguardMessageAreaController).setIsVisible(eq(false));
         verify(mKeyguardMessageAreaController).setMessage(eq(""));
     }
+
+    @Test
+    public void testShowBouncerOrKeyguard_needsFullScreen() {
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.SimPin);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        verify(mCentralSurfaces).hideKeyguard();
+        verify(mPrimaryBouncerInteractor).show(true);
+    }
+
+    @Test
+    public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.SimPin);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+        verify(mCentralSurfaces, never()).hideKeyguard();
+        verify(mPrimaryBouncerInteractor, never()).show(true);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 7f3d4b7..66c5aaa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -132,12 +132,6 @@
     }
 
     @Test
-    public void testAddNullCallback() {
-        mController.addCallback(null);
-        mController.fireConfigChanged(null);
-    }
-
-    @Test
     public void testModeChange() {
         List<Integer> states = List.of(
                 Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
index 692af6a..c1d11aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java
@@ -459,7 +459,7 @@
                         WalletCard.CARD_TYPE_UNKNOWN),
                 createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT),
                 createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT)
-                );
+        );
         GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0);
         mController.onWalletCardsRetrieved(response);
         mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt
new file mode 100644
index 0000000..e46c1f5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.wallet.util
+
+import android.service.quickaccesswallet.WalletCard
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Test class for WalletCardUtils */
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+@SmallTest
+class WalletCardUtilsTest : SysuiTestCase() {
+
+    private val paymentCard = createWalletCardWithType(WalletCard.CARD_TYPE_PAYMENT)
+    private val nonPaymentCard = createWalletCardWithType(WalletCard.CARD_TYPE_NON_PAYMENT)
+    private val unknownCard = createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN)
+
+    @Test
+    fun paymentCards_cardTypesAllUnknown_getsAllCards() {
+        val walletCardList =
+            mutableListOf(
+                createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN),
+                createWalletCardWithType(WalletCard.CARD_TYPE_UNKNOWN)
+            )
+
+        assertThat(walletCardList).isEqualTo(getPaymentCards(walletCardList))
+    }
+
+    @Test
+    fun paymentCards_cardTypesDifferent_onlyGetsPayment() {
+        val walletCardList = mutableListOf(paymentCard, nonPaymentCard, unknownCard)
+
+        assertThat(getPaymentCards(walletCardList)).isEqualTo(mutableListOf(paymentCard))
+    }
+
+    private fun createWalletCardWithType(cardType: Int): WalletCard {
+        return WalletCard.Builder(
+                /*cardId= */ CARD_ID,
+                /*cardType= */ cardType,
+                /*cardImage= */ mock(),
+                /*contentDescription=  */ CARD_DESCRIPTION,
+                /*pendingIntent= */ mock()
+            )
+            .build()
+    }
+
+    companion object {
+        private const val CARD_ID: String = "ID"
+        private const val CARD_DESCRIPTION: String = "Description"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index ef0adbb..d2c8aea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -41,6 +41,7 @@
 import com.android.wm.shell.onehanded.OneHandedEventCallback;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.sysui.ShellInterface;
 
@@ -79,6 +80,7 @@
     @Mock ShellExecutor mSysUiMainExecutor;
     @Mock NoteTaskInitializer mNoteTaskInitializer;
     @Mock DesktopMode mDesktopMode;
+    @Mock RecentTasks mRecentTasks;
 
     @Before
     public void setUp() {
@@ -91,6 +93,7 @@
                 Optional.of(mSplitScreen),
                 Optional.of(mOneHanded),
                 Optional.of(mDesktopMode),
+                Optional.of(mRecentTasks),
                 mCommandQueue,
                 mConfigurationController,
                 mKeyguardStateController,
@@ -129,4 +132,10 @@
                 any(DesktopModeTaskRepository.VisibleTasksListener.class),
                 any(Executor.class));
     }
+
+    @Test
+    public void initRecentTasks_registersListener() {
+        mWMShell.initRecentTasks(mRecentTasks);
+        verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any());
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 2b13dca..322fb28 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -56,20 +56,7 @@
         _isLockedOut.value = isLockedOut
     }
 
-    private val faceAuthPaused = MutableStateFlow(false)
-    override fun pauseFaceAuth() {
-        faceAuthPaused.value = true
-    }
-
-    override fun resumeFaceAuth() {
-        faceAuthPaused.value = false
-    }
-
-    fun isFaceAuthPaused(): Boolean {
-        return faceAuthPaused.value
-    }
-
-    override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+    override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
         _runningAuthRequest.value = uiEvent to fallbackToDetection
         _isAuthRunning.value = true
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
new file mode 100644
index 0000000..13437c9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -0,0 +1,64 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import javax.annotation.CheckReturnValue
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+class FakeQSTileDataInteractor<T>(
+    private val dataFlow: MutableSharedFlow<FakeData<T>> =
+        MutableSharedFlow(replay = Int.MAX_VALUE),
+    private val availabilityFlow: MutableSharedFlow<Boolean> =
+        MutableSharedFlow(replay = Int.MAX_VALUE),
+) : QSTileDataInteractor<T> {
+
+    private val mutableDataRequests = mutableListOf<QSTileDataRequest>()
+    val dataRequests: List<QSTileDataRequest> = mutableDataRequests
+
+    private val mutableAvailabilityRequests = mutableListOf<Unit>()
+    val availabilityRequests: List<Unit> = mutableAvailabilityRequests
+
+    @CheckReturnValue
+    fun emitData(data: T): FilterEmit =
+        object : FilterEmit {
+            override fun forRequest(request: QSTileDataRequest): Boolean =
+                dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request)))
+            override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any))
+        }
+
+    fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
+    suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
+
+    override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> {
+        mutableDataRequests.add(qsTileDataRequest)
+        return dataFlow
+            .filter {
+                when (it.filter) {
+                    is DataFilter.Any -> true
+                    is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest
+                }
+            }
+            .map { it.data }
+    }
+
+    override fun availability(): Flow<Boolean> {
+        mutableAvailabilityRequests.add(Unit)
+        return availabilityFlow
+    }
+
+    interface FilterEmit {
+        fun forRequest(request: QSTileDataRequest): Boolean
+        fun forAnyRequest(): Boolean
+    }
+
+    class FakeData<T>(
+        val data: T,
+        val filter: DataFilter,
+    )
+
+    sealed class DataFilter {
+        object Any : DataFilter()
+        class ForRequest(val request: QSTileDataRequest) : DataFilter()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
new file mode 100644
index 0000000..4e0266e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
+
+    private val mutex: Mutex = Mutex()
+    private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf()
+
+    val inputs: List<FakeInput<T>> = mutableInputs
+
+    fun lastInput(): FakeInput<T>? = inputs.lastOrNull()
+
+    override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) {
+        mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) }
+    }
+
+    data class FakeInput<T>(val userAction: QSTileUserAction, val data: T)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 08152a3..e72544a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -40,6 +40,23 @@
     @Deprecated("Use ShadeInteractor instead")
     override val legacyShadeExpansion = _legacyShadeExpansion
 
+    private val _legacyShadeTracking = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyShadeTracking = _legacyShadeTracking
+
+    private val _legacyQsTracking = MutableStateFlow(false)
+    @Deprecated("Use ShadeInteractor instead") override val legacyQsTracking = _legacyQsTracking
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyQsTracking(legacyQsTracking: Boolean) {
+        _legacyQsTracking.value = legacyQsTracking
+    }
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyShadeTracking(tracking: Boolean) {
+        _legacyShadeTracking.value = tracking
+    }
+
     fun setShadeModel(model: ShadeModel) {
         _shadeModel.value = model
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 05b6eb4..44ffb51 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -56,7 +56,6 @@
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerInternal;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -98,7 +97,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.LocalServices;
 import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
 import com.android.server.accessibility.magnification.MagnificationProcessor;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -1441,19 +1439,24 @@
                     AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback);
             return;
         }
-
         final long identity = Binder.clearCallingIdentity();
         try {
-            mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
-                final ScreenshotHardwareBuffer screenshotBuffer = LocalServices
-                        .getService(DisplayManagerInternal.class).userScreenshot(displayId);
-                if (screenshotBuffer != null) {
-                    sendScreenshotSuccess(screenshotBuffer, callback);
-                } else {
-                    sendScreenshotFailure(
-                            AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY, callback);
-                }
-            }, null).recycleOnUse());
+            ScreenCapture.ScreenCaptureListener screenCaptureListener = new
+                    ScreenCapture.ScreenCaptureListener(
+                    (screenshotBuffer, result) -> {
+                        if (screenshotBuffer != null && result == 0) {
+                            sendScreenshotSuccess(screenshotBuffer, callback);
+                        } else {
+                            sendScreenshotFailure(
+                                    AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+                                    callback);
+                        }
+                    }
+            );
+            mWindowManagerService.captureDisplay(displayId, null, screenCaptureListener);
+        } catch (Exception e) {
+            sendScreenshotFailure(AccessibilityService.ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY,
+                    callback);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1461,22 +1464,24 @@
 
     private void sendScreenshotSuccess(ScreenshotHardwareBuffer screenshotBuffer,
             RemoteCallback callback) {
-        final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
-        final ParcelableColorSpace colorSpace =
-                new ParcelableColorSpace(screenshotBuffer.getColorSpace());
+        mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+            final HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer();
+            final ParcelableColorSpace colorSpace =
+                    new ParcelableColorSpace(screenshotBuffer.getColorSpace());
 
-        final Bundle payload = new Bundle();
-        payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
-                AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
-        payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
-                hardwareBuffer);
-        payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
-        payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
-                SystemClock.uptimeMillis());
+            final Bundle payload = new Bundle();
+            payload.putInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS,
+                    AccessibilityService.TAKE_SCREENSHOT_SUCCESS);
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER,
+                    hardwareBuffer);
+            payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE, colorSpace);
+            payload.putLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP,
+                    SystemClock.uptimeMillis());
 
-        // Send back the result.
-        callback.sendResult(payload);
-        hardwareBuffer.close();
+            // Send back the result.
+            callback.sendResult(payload);
+            hardwareBuffer.close();
+        }, null).recycleOnUse());
     }
 
     private void sendScreenshotFailure(@AccessibilityService.ScreenshotErrorCode int errorCode,
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1e44de6..92e00ee 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -1 +1,8 @@
-package: "android.service.autofill"
\ No newline at end of file
+package: "android.service.autofill"
+
+flag {
+  name: "autofill_credman_integration"
+  namespace: "autofill"
+  description: "Guards Autofill Framework against Autofill-Credman integration"
+  bug: "296907283"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
index 8996926..44f1e76 100644
--- a/services/core/java/com/android/server/SmartStorageMaintIdler.java
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class SmartStorageMaintIdler extends JobService {
     private static final String TAG = "SmartStorageMaintIdler";
@@ -34,15 +35,15 @@
 
     private static final int SMART_MAINT_JOB_ID = 2808;
 
-    private boolean mStarted;
+    private final AtomicBoolean mStarted = new AtomicBoolean(false);
     private JobParameters mJobParams;
     private final Runnable mFinishCallback = new Runnable() {
         @Override
         public void run() {
             Slog.i(TAG, "Got smart storage maintenance service completion callback");
-            if (mStarted) {
+            if (mStarted.get()) {
                 jobFinished(mJobParams, false);
-                mStarted = false;
+                mStarted.set(false);
             }
             // ... and try again in a next period
             scheduleSmartIdlePass(SmartStorageMaintIdler.this,
@@ -52,18 +53,26 @@
 
     @Override
     public boolean onStartJob(JobParameters params) {
-        mJobParams = params;
-        StorageManagerService ms = StorageManagerService.sSelf;
-        if (ms != null) {
-            mStarted = true;
-            ms.runSmartIdleMaint(mFinishCallback);
+        final StorageManagerService ms = StorageManagerService.sSelf;
+        if (mStarted.compareAndSet(false, true)) {
+            new Thread() {
+                public void run() {
+                    mJobParams = params;
+                    if (ms != null) {
+                        ms.runSmartIdleMaint(mFinishCallback);
+                    } else {
+                        mStarted.set(false);
+                    }
+                }
+            }.start();
+            return ms != null;
         }
-        return ms != null;
+        return false;
     }
 
     @Override
     public boolean onStopJob(JobParameters params) {
-        mStarted = false;
+        mStarted.set(false);
         return false;
     }
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index a787644..25ca509c 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2842,7 +2842,7 @@
         return true;
     }
 
-    void runSmartIdleMaint(Runnable callback) {
+    synchronized void runSmartIdleMaint(Runnable callback) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
 
         try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5911f6..50be128 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -315,6 +315,7 @@
 import android.net.Proxy;
 import android.net.Uri;
 import android.os.AppZygote;
+import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.BinderProxy;
@@ -4070,21 +4071,6 @@
                         profile.addPss(mi.getTotalPss(),
                                 mi.getTotalUss(), mi.getTotalRss(), false,
                                 ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration);
-                        proc.getPkgList().forEachPackageProcessStats(holder -> {
-                            final ProcessState state = holder.state;
-                            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
-                                    proc.info.uid,
-                                    state != null ? state.getName() : proc.processName,
-                                    state != null ? state.getPackage() : proc.info.packageName,
-                                    mi.getTotalPss(),
-                                    mi.getTotalUss(),
-                                    mi.getTotalRss(),
-                                    ProcessStats.ADD_PSS_EXTERNAL_SLOW,
-                                    duration,
-                                    holder.appVersion,
-                                    profile.getCurrentHostingComponentTypes(),
-                                    profile.getHistoricalHostingComponentTypes());
-                        });
                     }
                 }
             }
@@ -4131,20 +4117,6 @@
                         // Record this for posterity if the process has been stable.
                         profile.addPss(pi, tmpUss[0], tmpUss[2], false,
                                 ProcessStats.ADD_PSS_EXTERNAL, duration);
-                        proc.getPkgList().forEachPackageProcessStats(holder -> {
-                            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
-                                    proc.info.uid,
-                                    holder.state.getName(),
-                                    holder.state.getPackage(),
-                                    pi,
-                                    tmpUss[0],
-                                    tmpUss[2],
-                                    ProcessStats.ADD_PSS_EXTERNAL,
-                                    duration,
-                                    holder.appVersion,
-                                    profile.getCurrentHostingComponentTypes(),
-                                    profile.getHistoricalHostingComponentTypes());
-                        });
                     }
                 }
             }
@@ -9851,6 +9823,10 @@
         PriorityDump.dump(mPriorityDumper, fd, pw, args);
     }
 
+    private static final String TICK =
+            "---------------------------------------"
+            + "----------------------------------------";
+
     private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
             boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient,
             boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) {
@@ -9906,6 +9882,11 @@
                 sdumper.dumpLocked();
             }
         }
+
+        // No need to hold the lock.
+        pw.println(TICK);
+        AnrTimer.dump(pw, false);
+
         // We drop the lock here because we can't call dumpWithClient() with the lock held;
         // if the caller wants a consistent state for the !dumpClient case, it can call this
         // method with the lock held.
@@ -10351,6 +10332,8 @@
                     mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
                     mOomAdjuster.dumpCacheOomRankerSettings(pw);
                 }
+            } else if ("timers".equals(cmd)) {
+                AnrTimer.dump(pw, true);
             } else if ("services".equals(cmd) || "s".equals(cmd)) {
                 if (dumpClient) {
                     ActiveServices.ServiceDumper dumper;
@@ -12077,17 +12060,6 @@
                         // Record this for posterity if the process has been stable.
                         r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
                                 reportType, endTime - startTime);
-                        r.getPkgList().forEachPackageProcessStats(holder -> {
-                            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
-                                    r.info.uid,
-                                    holder.state.getName(),
-                                    holder.state.getPackage(),
-                                    myTotalPss, myTotalUss, myTotalRss, reportType,
-                                    endTime-startTime,
-                                    holder.appVersion,
-                                    r.mProfile.getCurrentHostingComponentTypes(),
-                                    r.mProfile.getHistoricalHostingComponentTypes());
-                        });
                     }
                 }
 
@@ -12723,16 +12695,6 @@
                     // Record this for posterity if the process has been stable.
                     r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
                                 reportType, endTime - startTime);
-                    r.getPkgList().forEachPackageProcessStats(holder -> {
-                        FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
-                                r.info.uid,
-                                holder.state.getName(),
-                                holder.state.getPackage(),
-                                myTotalPss, myTotalUss, myTotalRss, reportType, endTime-startTime,
-                                holder.appVersion,
-                                r.mProfile.getCurrentHostingComponentTypes(),
-                                r.mProfile.getHistoricalHostingComponentTypes());
-                    });
                 }
             }
 
@@ -15134,6 +15096,16 @@
             }
         }
 
+        // STOPSHIP(b/298884211):  Remove this logging
+        if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+            final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+            if (level < 0) {
+                Slog.wtf(BroadcastQueue.TAG, "Unexpected broadcast: " + intent
+                        + "; callingUid: " + callingUid + ", callingPid: " + callingPid,
+                        new Throwable());
+            }
+        }
+
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
@@ -15866,7 +15838,15 @@
         activeInstr.mWatcher = watcher;
         activeInstr.mUiAutomationConnection = uiAutomationConnection;
         activeInstr.mResultClass = className;
-        activeInstr.mHasBackgroundActivityStartsPermission = false;
+        activeInstr.mHasBackgroundActivityStartsPermission =
+                isSdkInSandbox
+                        // TODO(b/261864298): consider using START_ACTIVITIES_FROM_BACKGROUND.
+                        && checkPermission(
+                                        android.Manifest.permission
+                                                .START_ACTIVITIES_FROM_SDK_SANDBOX,
+                                        Binder.getCallingPid(),
+                                        Binder.getCallingUid())
+                                == PackageManager.PERMISSION_GRANTED;
         activeInstr.mHasBackgroundForegroundServiceStartsPermission = false;
         // Instrumenting sdk sandbox without a restart is not supported
         activeInstr.mNoRestart = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 942d35a..a057f32 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4095,6 +4095,7 @@
             pw.println("    lru: raw LRU process list");
             pw.println("    binder-proxies: stats on binder objects and IPCs");
             pw.println("    settings: currently applied config settings");
+            pw.println("    timers: the current ANR timer state");
             pw.println("    service [COMP_SPEC]: service client-side state");
             pw.println("    package [PACKAGE_NAME]: all state related to given package");
             pw.println("    all: dump all activities");
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 7d98443..e0a2246 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -20,6 +20,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.content.pm.ApplicationInfo;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.Trace;
 import android.util.ArraySet;
@@ -267,6 +268,7 @@
     private class AnrRecord {
         final ProcessRecord mApp;
         final int mPid;
+        final int mUid;
         final String mActivityShortComponentName;
         final String mParentShortComponentName;
         final TimeoutRecord mTimeoutRecord;
@@ -283,6 +285,7 @@
                 Future<File> firstPidFilePromise) {
             mApp = anrProcess;
             mPid = anrProcess.mPid;
+            mUid = anrProcess.uid;
             mActivityShortComponentName = activityShortComponentName;
             mParentShortComponentName = parentShortComponentName;
             mTimeoutRecord = timeoutRecord;
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
new file mode 100644
index 0000000..cd6f009
--- /dev/null
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -0,0 +1,834 @@
+/*
+ * Copyright (C) 2023 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.text.TextUtils.formatSimple;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcessCpuTracker;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class managers AnrTimers.  An AnrTimer is a substitute for a delayed Message.  In legacy
+ * mode, the timer just sends a delayed message.  In modern mode, the timer is implemented in
+ * native code; on expiration, the message is sent without delay.
+ *
+ * <p>There are four external operations on a timer:
+ * <ul>
+ *
+ * <li>{@link #start} starts a timer.  The timer is started with an object that the message
+ * argument.  The timer is also given the pid and uid of the target. A timer that is started must
+ * be canceled, accepted, or discarded.
+ *
+ * <li>{@link #cancel} stops a timer and removes any in-flight expiration messages.
+ *
+ * <li>{@link #accept} acknowledges that the timer has expired, and that an ANR should be
+ * generated.  This clears bookkeeping information for the timer.
+ *
+ * <li>{@link #discard} acknowledges that the timer has expired but, for other reasons, no ANR
+ * will be generated.  This clears bookkeeping information for the timer.
+ *
+ *</li></p>
+ *
+ * <p>There is one internal operation on a timer: {@link #expire}.  A timer may have automatic
+ * extensions enabled.  If so, the extension is computed and if the extension is non-zero, the timer
+ * is restarted with the extension timeout.  If extensions are disabled or if the extension is zero,
+ * the client process is notified of the expiration.
+ *
+ * @hide
+ */
+abstract class AnrTimer<V> {
+
+    /**
+     * The log tag.
+     */
+    final static String TAG = "AnrTimer";
+
+    /**
+     * The trace track for these events.  There is a single track for all AnrTimer instances.  The
+     * tracks give a sense of handler latency: the time between timer expiration and ANR
+     * collection.
+     */
+    private final static String TRACK = "AnrTimer";
+
+    /**
+     * Enable debug messages.
+     */
+    private static boolean DEBUG = false;
+
+    /**
+     * The trace tag.
+     */
+    private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+    /**
+     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
+     * used to diagnose long latencies in the client.
+     */
+    private static final boolean ENABLE_TRACING = false;
+
+    /**
+     * The status of an ANR timer.  TIMER_INVALID status is returned when an error is detected.
+     */
+    private static final int TIMER_INVALID = 0;
+    private static final int TIMER_RUNNING = 1;
+    private static final int TIMER_EXPIRED = 2;
+
+    @IntDef(prefix = { "TIMER_" }, value = {
+                TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
+            })
+    private @interface TimerStatus {}
+
+    /**
+     * A static list of all known AnrTimer instances, used for dumping and testing.
+     */
+    @GuardedBy("sAnrTimerList")
+    private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+
+    /**
+     * An error is defined by its issue, the operation that detected the error, the tag of the
+     * affected service, a short stack of the bad call, and the stringified arg associated with
+     * the error.
+     */
+    private static final class Error {
+        /** The issue is the kind of error that was detected.  This is a free-form string. */
+        final String issue;
+        /** The operation that detected the error: start, cancel, accept, or discard. */
+        final String operation;
+        /** The argument (stringified) passed in to the operation. */
+        final String arg;
+        /** The tag of the associated AnrTimer. */
+        final String tag;
+        /** A partial stack that localizes the caller of the operation. */
+        final StackTraceElement[] stack;
+
+        Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
+                @NonNull StackTraceElement[] stack, @NonNull String arg) {
+            this.issue = issue;
+            this.operation = operation;
+            this.tag = tag;
+            this.stack = stack;
+            this.arg = arg;
+        }
+    }
+
+    /**
+     * A list of errors detected during processing.  Errors correspond to "timer not found"
+     * conditions.  The stack trace identifies the source of the call.  The list is
+     * first-in/first-out, and the size is limited to MAX_SAVED_ERROR_COUNT.
+     */
+    @GuardedBy("sErrors")
+    private static final ArrayList<Error> sErrors = new ArrayList<>();
+
+    /**
+     * The maximum number of errors that are saved in the sErrors list.
+     */
+    private static final int MAX_SAVED_ERROR_COUNT = 20;
+
+    /**
+     * A record of a single anr timer.  The pid and uid are retained for reference but they do not
+     * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
+     * through the owner field.  Access to timer fields is guarded by the mLock of the owner.
+     */
+    private static class Timer {
+        /** The AnrTimer that is managing this Timer. */
+        final AnrTimer owner;
+
+        /** The argument that uniquely identifies the Timer in the context of its current owner. */
+        final Object arg;
+        /** The pid of the process being tracked by this Timer. */
+        final int pid;
+        /** The uid of the process being tracked by this Timer as reported by the kernel. */
+        final int uid;
+        /** The original timeout. */
+        final long timeoutMs;
+
+        /** The status of the Timer.  */
+        @GuardedBy("owner.mLock")
+        @TimerStatus
+        int status;
+
+        /** The absolute time the timer was startd */
+        final long startedMs;
+
+        /** Fields used by the native timer service. */
+
+        /** The timer ID: used to exchange information with the native service. */
+        int timerId;
+
+        /** Fields used by the legacy timer service. */
+
+        /**
+         * The process's cpu delay time when the timer starts . It is meaningful only if
+         * extendable is true.  The cpu delay is cumulative, so the incremental delay that occurs
+         * during a timer is the delay at the end of the timer minus this value.  Units are in
+         * milliseconds.
+         */
+        @GuardedBy("owner.mLock")
+        long initialCpuDelayMs;
+
+        /** True if the timer has been extended. */
+        @GuardedBy("owner.mLock")
+        boolean extended;
+
+        /**
+         * Fetch a new Timer.  This is private.  Clients should get a new timer using the obtain()
+         * method.
+         */
+        private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
+                @NonNull AnrTimer service) {
+            this.arg = arg;
+            this.pid = pid;
+            this.uid = uid;
+            this.timerId = 0;
+            this.timeoutMs = timeoutMs;
+            this.startedMs = now();
+            this.owner = service;
+            this.initialCpuDelayMs = 0;
+            this.extended = false;
+            this.status = TIMER_INVALID;
+        }
+
+        /** Get a timer.  This implementation constructs a new timer. */
+        static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
+                @NonNull AnrTimer service) {
+            return new Timer(pid, uid, arg, timeout, service);
+        }
+
+        /** Release a timer. This implementation simply drops the timer. */
+        void release() {
+        }
+
+        /** Return the age of the timer. This is used for debugging. */
+        long age() {
+            return now() - startedMs;
+        }
+
+        /**
+         * The hash code is generated from the owner and the argument.  By definition, the
+         * combination must be unique for the lifetime of an in-use Timer.
+         */
+        @Override
+        public int hashCode() {
+            return Objects.hash(owner, arg);
+        }
+
+        /**
+         * The equality check compares the owner and the argument.  By definition, the combination
+         * must be unique for the lifetime of an in-use Timer.
+         */
+        @Override
+        public boolean equals(Object r) {
+            if (r instanceof Timer) {
+                Timer t = (Timer) r;
+                return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            final int myStatus;
+            synchronized (owner.mLock) {
+                myStatus = status;
+            }
+            return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
+                    + " " + statusString(myStatus) + " " + owner.mLabel;
+        }
+    }
+
+    /** A lock for the AnrTimer instance. */
+    private final Object mLock = new Object();
+
+    /**
+     * The map from client argument to the associated timer.
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
+
+    /** The highwater mark of started, but not closed, timers. */
+    @GuardedBy("mLock")
+    private int mMaxStarted = 0;
+
+    /**
+     * The total number of timers started.
+     */
+    @GuardedBy("mLock")
+    private int mTotalStarted = 0;
+
+    /**
+     * The total number of errors detected.
+     */
+    @GuardedBy("mLock")
+    private int mTotalErrors = 0;
+
+    /**
+     * The total number of timers that have expired.
+     */
+    @GuardedBy("mLock")
+    private int mTotalExpired = 0;
+
+    /**
+     * A TimerService that generates a timeout event <n> milliseconds in the future.  See the
+     * class documentation for an explanation of the operations.
+     */
+    private abstract class TimerService {
+        /** Start a timer.  The timeout must be initialized. */
+        abstract boolean start(@NonNull Timer timer);
+
+        abstract void cancel(@NonNull Timer timer);
+
+        abstract void accept(@NonNull Timer timer);
+
+        abstract void discard(@NonNull Timer timer);
+    }
+
+    /**
+     * A class to assist testing.  All methods are null by default but can be overridden as
+     * necessary for a test.
+     */
+    @VisibleForTesting
+    static class Injector {
+        /**
+         * Return a handler for the given Callback.
+         */
+        Handler getHandler(@NonNull Handler.Callback callback) {
+            return null;
+        };
+
+        /**
+         * Return a CpuTracker.
+         */
+        CpuTracker getTracker() {
+            return null;
+        }
+    }
+
+    /**
+     * A helper class to measure CPU delays.  Given a process ID, this class will return the
+     * cumulative CPU delay for the PID, since process inception.  This class is defined to assist
+     * testing.
+     */
+    @VisibleForTesting
+    static class CpuTracker {
+        /**
+         * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
+         * single process and not on the collection of threads associated with that process.
+         */
+        private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
+
+        /** A simple wrapper to fetch the delay.  This method can be overridden for testing. */
+        long delay(int pid) {
+            return mCpu.getCpuDelayTimeForPid(pid);
+        }
+    }
+
+    /**
+     * The "user-space" implementation of the timer service.  This service uses its own message
+     * handler to create timeouts.
+     */
+    private class HandlerTimerService extends TimerService {
+        /** The lock for this handler */
+        private final Object mLock = new Object();
+
+        /** The message handler for scheduling future events. */
+        private final Handler mHandler;
+
+        /** The interface to fetch process statistics that might extend an ANR timeout. */
+        private final CpuTracker mCpu;
+
+        /** Create a HandlerTimerService based on the input handler. */
+        HandlerTimerService(@NonNull Handler handler) {
+            mHandler = new Handler(handler.getLooper(), this::expires);
+            mCpu = new CpuTracker();
+        }
+
+        /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
+        @VisibleForTesting
+        HandlerTimerService(@NonNull Injector injector) {
+            mHandler = injector.getHandler(this::expires);
+            mCpu = injector.getTracker();
+        }
+
+        /** Post a message with the specified timeout.  The timer is not modified. */
+        private void post(@NonNull Timer t, long timeoutMillis) {
+            final Message msg = mHandler.obtainMessage();
+            msg.obj = t;
+            mHandler.sendMessageDelayed(msg, timeoutMillis);
+        }
+
+        /**
+         * The local expiration handler first attempts to compute a timer extension.  If the timer
+         * should be extended, it is rescheduled in the future (granting more time to the
+         * associated process).  If the timer should not be extended then the timeout is delivered
+         * to the client.
+         *
+         * A process is extended to account for the time the process was swapped out and was not
+         * runnable through no fault of its own.  A timer can only be extended once and only if
+         * the AnrTimer permits extensions.  Finally, a timer will never be extended by more than
+         * the original timeout, so the total timeout will never be more than twice the originally
+         * configured timeout.
+         */
+        private boolean expires(Message msg) {
+            Timer t = (Timer) msg.obj;
+            synchronized (mLock) {
+                long extension = 0;
+                if (mExtend && !t.extended) {
+                    extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
+                    if (extension < 0) extension = 0;
+                    if (extension > t.timeoutMs) extension = t.timeoutMs;
+                    t.extended = true;
+                }
+                if (extension > 0) {
+                    post(t, extension);
+                } else {
+                    onExpiredLocked(t, now());
+                }
+            }
+            return true;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        boolean start(@NonNull Timer t) {
+            if (mExtend) {
+                t.initialCpuDelayMs = mCpu.delay(t.pid);
+            }
+            post(t, t.timeoutMs);
+            return true;
+        }
+
+        @Override
+        void cancel(@NonNull Timer t) {
+            mHandler.removeMessages(0, t);
+        }
+
+        @Override
+        void accept(@NonNull Timer t) {
+            // Nothing to do.
+        }
+
+        @Override
+        void discard(@NonNull Timer t) {
+            // Nothing to do.
+        }
+
+        /** The string identifies this subclass of AnrTimerService as being based on handlers. */
+        @Override
+        public String toString() {
+            return "handler";
+        }
+    }
+
+    /**
+     * The handler for messages sent from this instance.
+     */
+    private final Handler mHandler;
+
+    /**
+     * The message type for messages sent from this interface.
+     */
+    private final int mWhat;
+
+    /**
+     * A label that identifies the AnrTimer associated with a Timer in log messages.
+     */
+    private final String mLabel;
+
+    /**
+     * Whether this timer instance supports extending timeouts.
+     */
+    private final boolean mExtend;
+
+    /**
+     * The timer service to use for this AnrTimer.
+     */
+    private final TimerService mTimerService;
+
+    /**
+     * Whether or not canceling a non-existent timer is an error.  Clients often cancel freely
+     * preemptively, without knowing if the timer was ever started.  Keeping this variable true
+     * means that such behavior is not an error.
+     */
+    private final boolean mLenientCancel = true;
+
+    /**
+     * The common constructor.  A null injector results in a normal, production timer.
+     */
+    @VisibleForTesting
+    AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
+            @Nullable Injector injector) {
+        mHandler = handler;
+        mWhat = what;
+        mLabel = label;
+        mExtend = extend;
+        if (injector == null) {
+            mTimerService = new HandlerTimerService(handler);
+        } else {
+            mTimerService = new HandlerTimerService(injector);
+        }
+        synchronized (sAnrTimerList) {
+            sAnrTimerList.add(new WeakReference(this));
+        }
+        Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService.toString(), label));
+    }
+
+    /**
+     * Create one timer instance for production.  The client can ask for extensible timeouts.
+     */
+    AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
+        this(handler, what, label, extend, null);
+    }
+
+    /**
+     * Create one timer instance for production.  There are no extensible timeouts.
+     */
+    AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
+        this(handler, what, label, false, null);
+    }
+
+    /**
+     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
+     */
+    private void traceBegin(Timer t, String what) {
+        if (ENABLE_TRACING) {
+            final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
+            final int cookie = t.hashCode();
+            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
+        }
+    }
+
+    /**
+     * End a trace on the timer.
+     */
+    private void traceEnd(Timer t) {
+        if (ENABLE_TRACING) {
+            final int cookie = t.hashCode();
+            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
+        }
+    }
+
+    /**
+     * Return the string representation for a timer status.
+     */
+    private static String statusString(int s) {
+        switch (s) {
+            case TIMER_INVALID: return "invalid";
+            case TIMER_RUNNING: return "running";
+            case TIMER_EXPIRED: return "expired";
+        }
+        return formatSimple("unknown: %d", s);
+    }
+
+    /**
+     * Delete the timer associated with arg from the maps and return it.  Return null if the timer
+     * was not found.
+     */
+    @GuardedBy("mLock")
+    private Timer removeLocked(V arg) {
+        Timer timer = mTimerMap.remove(arg);
+        return timer;
+    }
+
+    /**
+     * Return the number of timers currently running.
+     */
+    @VisibleForTesting
+    static int sizeOfTimerList() {
+        synchronized (sAnrTimerList) {
+            int totalTimers = 0;
+            for (int i = 0; i < sAnrTimerList.size(); i++) {
+                AnrTimer client = sAnrTimerList.get(i).get();
+                if (client != null) totalTimers += client.mTimerMap.size();
+            }
+            return totalTimers;
+        }
+    }
+
+    /**
+     * Clear out all existing timers.  This will lead to unexpected behavior if used carelessly.
+     * It is available only for testing.  It returns the number of times that were actually
+     * erased.
+     */
+    @VisibleForTesting
+    static int resetTimerListForHermeticTest() {
+        synchronized (sAnrTimerList) {
+            int mapLen = 0;
+            for (int i = 0; i < sAnrTimerList.size(); i++) {
+                AnrTimer client = sAnrTimerList.get(i).get();
+                if (client != null) {
+                    mapLen += client.mTimerMap.size();
+                    client.mTimerMap.clear();
+                }
+            }
+            if (mapLen > 0) {
+                Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
+            }
+            return mapLen;
+        }
+    }
+
+    /**
+     * Report something about a timer.
+     */
+    private void report(@NonNull Timer timer, @NonNull String msg) {
+        Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
+    }
+
+   /**
+     * Start a timer.
+     */
+    boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, this);
+        synchronized (mLock) {
+            Timer old = mTimerMap.get(arg);
+            if (old != null) {
+                // There is an existing timer.  This is a protocol error in the client.  Record
+                // the error and then clean up by canceling running timers and discarding expired
+                // timers.
+                restartedLocked(old.status, arg);
+                if (old.status == TIMER_EXPIRED) {
+                    discard(arg);
+                } else {
+                    cancel(arg);
+                }
+            }
+            if (mTimerService.start(timer)) {
+                timer.status = TIMER_RUNNING;
+                mTimerMap.put(arg, timer);
+                mTotalStarted++;
+                mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
+                if (DEBUG) report(timer, "start");
+                return true;
+            } else {
+                Log.e(TAG, "AnrTimer.start failed");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Cancel a timer.  Return false if the timer was not found.
+     */
+    boolean cancel(@NonNull V arg) {
+        synchronized (mLock) {
+            Timer timer = removeLocked(arg);
+            if (timer == null) {
+                if (!mLenientCancel) notFoundLocked("cancel", arg);
+                return false;
+            }
+            mTimerService.cancel(timer);
+            // There may be an expiration message in flight.  Cancel it.
+            mHandler.removeMessages(mWhat, arg);
+            if (DEBUG) report(timer, "cancel");
+            timer.release();
+            return true;
+        }
+    }
+
+    /**
+     * Accept a timer in the framework-level handler.  The timeout has been accepted and the
+     * timeout handler is executing.  Return false if the timer was not found.
+     */
+    boolean accept(@NonNull V arg) {
+        synchronized (mLock) {
+            Timer timer = removeLocked(arg);
+            if (timer == null) {
+                notFoundLocked("accept", arg);
+                return false;
+            }
+            mTimerService.accept(timer);
+            traceEnd(timer);
+            if (DEBUG) report(timer, "accept");
+            timer.release();
+            return true;
+        }
+    }
+
+    /**
+     * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
+     * longer interesting.  No statistics are collected.  Return false if the time was not found.
+     */
+    boolean discard(@NonNull V arg) {
+        synchronized (mLock) {
+            Timer timer = removeLocked(arg);
+            if (timer == null) {
+                notFoundLocked("discard", arg);
+                return false;
+            }
+            mTimerService.discard(timer);
+            traceEnd(timer);
+            if (DEBUG) report(timer, "discard");
+            timer.release();
+            return true;
+        }
+    }
+
+    /**
+     * The notifier that a timer has fired.  The timer is not modified.
+     */
+    @GuardedBy("mLock")
+    private void onExpiredLocked(@NonNull Timer timer, long when) {
+        if (DEBUG) report(timer, "expire");
+        traceBegin(timer, "expired");
+        mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
+        synchronized (mLock) {
+            mTotalExpired++;
+        }
+    }
+
+    /**
+     * Dump a single AnrTimer.
+     */
+    private void dump(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.format("timer: %s\n", mLabel);
+            pw.increaseIndent();
+            pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
+                    mTotalStarted, mMaxStarted, mTimerMap.size(),
+                    mTotalExpired, mTotalErrors);
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
+     * Enable or disable debugging.
+     */
+    static void debug(boolean f) {
+        DEBUG = f;
+    }
+
+    /**
+     * The current time in milliseconds.
+     */
+    private static long now() {
+        return SystemClock.uptimeMillis();
+    }
+
+    /**
+     * Log an error.  A limited stack trace leading to the client call that triggered the error is
+     * recorded.  The stack trace assumes that this method is not called directly.
+     *
+     * If DEBUG is true, a log message is generated as well.
+     */
+    @GuardedBy("mLock")
+    private void recordErrorLocked(String operation, String errorMsg, Object arg) {
+        StackTraceElement[] s = Thread.currentThread().getStackTrace();
+        final String what = Objects.toString(arg);
+        // The copy range starts at the caller of the timer operation, and includes three levels.
+        // This should be enough to isolate the location of the call.
+        StackTraceElement[] location = Arrays.copyOfRange(s, 6, 9);
+        synchronized (sErrors) {
+            // Ensure the error list does not grow beyond the limit.
+            while (sErrors.size() >= MAX_SAVED_ERROR_COUNT) {
+                sErrors.remove(0);
+            }
+            // Add the new error to the list.
+            sErrors.add(new Error(errorMsg, operation, mLabel, location, what));
+        }
+        if (DEBUG) Log.w(TAG, operation + " " + errorMsg + " " + mLabel + " timer " + what);
+        mTotalErrors++;
+    }
+
+    /**
+     * Log an error about  a timer not found.
+     */
+    @GuardedBy("mLock")
+    private void notFoundLocked(String operation, Object arg) {
+        recordErrorLocked(operation, "notFound", arg);
+    }
+
+    /**
+     * Log an error about a timer that is started when there is an existing timer.
+     */
+    @GuardedBy("mLock")
+    private void restartedLocked(@TimerStatus int status, Object arg) {
+        recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
+    }
+
+    /**
+     * Dump a single error to the output stream.
+     */
+    private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
+        ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
+                err.issue, err.arg);
+        ipw.increaseIndent();
+        for (int i = 0; i < err.stack.length; i++) {
+            ipw.println("    " + err.stack[i].toString());
+        }
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * Dump all errors to the output stream.
+     */
+    private static void dumpErrors(IndentingPrintWriter ipw) {
+        ArrayList<Error> errors;
+        synchronized (sErrors) {
+            if (sErrors.size() == 0) return;
+            errors = (ArrayList<Error>) sErrors.clone();
+        }
+        ipw.println("Errors");
+        ipw.increaseIndent();
+        for (int i = 0; i < errors.size(); i++) {
+            dump(ipw, i, errors.get(i));
+        }
+        ipw.decreaseIndent();
+    }
+
+    /**
+     * Dumpsys output.
+     */
+    static void dump(@NonNull PrintWriter pw, boolean verbose) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+        ipw.println("AnrTimer statistics");
+        ipw.increaseIndent();
+        synchronized (sAnrTimerList) {
+            for (int i = 0; i < sAnrTimerList.size(); i++) {
+                AnrTimer client = sAnrTimerList.get(i).get();
+                if (client != null) client.dump(ipw);
+            }
+        }
+        if (verbose) dumpErrors(ipw);
+        ipw.format("AnrTimerEnd\n");
+        ipw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 46e5523..928b5d8 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -770,17 +770,6 @@
                 swapPss * 1024, rss * 1024, statType, procState, pssDuration);
         profile.setLastPssTime(now);
         profile.addPss(pss, uss, rss, true, statType, pssDuration);
-        proc.getPkgList().forEachPackageProcessStats(holder -> {
-            FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
-                    proc.info.uid,
-                    holder.state.getName(),
-                    holder.state.getPackage(),
-                    pss, uss, rss,
-                    statType, pssDuration,
-                    holder.appVersion,
-                    profile.getCurrentHostingComponentTypes(),
-                    profile.getHistoricalHostingComponentTypes());
-        });
         if (DEBUG_PSS) {
             Slog.d(TAG_PSS,
                     "pss of " + proc.toShortString() + ": " + pss
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index fc8175b..c35a3b2 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -101,10 +101,9 @@
     boolean runningOomAdjusted;
 
     /**
-     * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
-     * used when deciding if we should extend the soft ANR timeout.
+     * True if a timer has been started against this queue.
      */
-    long lastCpuDelayTime;
+    private boolean mTimeoutScheduled;
 
     /**
      * Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before
@@ -1344,6 +1343,21 @@
         return head;
     }
 
+    /**
+     * Set the timeout flag to indicate that an ANR timer has been started.  A value of true means a
+     * timer is running; a value of false means there is no timer running.
+     */
+    void setTimeoutScheduled(boolean timeoutStarted) {
+        mTimeoutScheduled = timeoutStarted;
+    }
+
+    /**
+     * Get the timeout flag
+     */
+    boolean timeoutScheduled() {
+        return mTimeoutScheduled;
+    }
+
     @Override
     public String toString() {
         if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8d2edaa..6a41628 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -59,6 +59,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.BatteryManager;
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.Handler;
@@ -149,6 +150,8 @@
         // We configure runnable size only once at boot; it'd be too complex to
         // try resizing dynamically at runtime
         mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
+
+        mAnrTimer = new BroadcastAnrTimer(mLocalHandler);
     }
 
     /**
@@ -242,14 +245,19 @@
      */
     private @UptimeMillisLong long mLastTestFailureTime;
 
+    /**
+     * The ANR timer service for broadcasts.
+     */
+    @GuardedBy("mService")
+    private final BroadcastAnrTimer mAnrTimer;
+
     private static final int MSG_UPDATE_RUNNING_LIST = 1;
-    private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
-    private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
-    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
-    private static final int MSG_CHECK_HEALTH = 5;
-    private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
-    private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
-    private static final int MSG_UID_STATE_CHANGED = 8;
+    private static final int MSG_DELIVERY_TIMEOUT = 2;
+    private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
+    private static final int MSG_CHECK_HEALTH = 4;
+    private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 5;
+    private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
+    private static final int MSG_UID_STATE_CHANGED = 7;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -264,12 +272,8 @@
                 updateRunningList();
                 return true;
             }
-            case MSG_DELIVERY_TIMEOUT_SOFT: {
-                deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1);
-                return true;
-            }
-            case MSG_DELIVERY_TIMEOUT_HARD: {
-                deliveryTimeoutHard((BroadcastProcessQueue) msg.obj);
+            case MSG_DELIVERY_TIMEOUT: {
+                deliveryTimeout((BroadcastProcessQueue) msg.obj);
                 return true;
             }
             case MSG_BG_ACTIVITY_START_TIMEOUT: {
@@ -1024,12 +1028,12 @@
         // immediately assume delivery success
         final boolean assumeDelivered = r.isAssumedDelivered(index);
         if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
-            queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
-
+            queue.setTimeoutScheduled(true);
             final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
                     : mBgConstants.TIMEOUT);
-            mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
-                    MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
+            mAnrTimer.start(queue, softTimeoutMillis);
+        } else {
+            queue.setTimeoutScheduled(false);
         }
 
         if (r.mBackgroundStartPrivileges.allowsAny()) {
@@ -1074,6 +1078,16 @@
                 queue.lastProcessState = app.mState.getCurProcState();
                 if (receiver instanceof BroadcastFilter) {
                     notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
+                    // STOPSHIP(b/298884211):  Remove this logging
+                    if (Intent.ACTION_BATTERY_CHANGED.equals(receiverIntent.getAction())) {
+                        int level = receiverIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+                        if (level < 0) {
+                            Slog.wtf(TAG, "Dispatching unexpected broadcast: " + receiverIntent
+                                    + " to " + receiver
+                                    + "; callingUid: " + r.callingUid
+                                    + ", callingPid: " + r.callingPid);
+                        }
+                    }
                     thread.scheduleRegisteredReceiver(
                         ((BroadcastFilter) receiver).receiverList.receiver,
                         receiverIntent, r.resultCode, r.resultData, r.resultExtras,
@@ -1107,7 +1121,7 @@
                 // If we were trying to deliver a manifest broadcast, throw the error as we need
                 // to try redelivering the broadcast to this receiver.
                 if (receiver instanceof ResolveInfo) {
-                    mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+                    mAnrTimer.cancel(queue);
                     throw new BroadcastDeliveryFailedException(e);
                 }
                 finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
@@ -1156,41 +1170,29 @@
         r.resultTo = null;
     }
 
-    private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue,
-            int softTimeoutMillis) {
+    private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) {
         synchronized (mService) {
-            deliveryTimeoutSoftLocked(queue, softTimeoutMillis);
+            deliveryTimeoutLocked(queue);
         }
     }
 
-    private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
-            int softTimeoutMillis) {
-        if (queue.app != null) {
-            // Instead of immediately triggering an ANR, extend the timeout by
-            // the amount of time the process was runnable-but-waiting; we're
-            // only willing to do this once before triggering an hard ANR
-            final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
-            final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
-            mLocalHandler.sendMessageDelayed(
-                    Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
-                    hardTimeoutMillis);
-        } else {
-            deliveryTimeoutHardLocked(queue);
-        }
-    }
-
-    private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) {
-        synchronized (mService) {
-            deliveryTimeoutHardLocked(queue);
-        }
-    }
-
-    private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
+    private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
         finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
-                "deliveryTimeoutHardLocked");
+                "deliveryTimeoutLocked");
         demoteFromRunningLocked(queue);
     }
 
+    private class BroadcastAnrTimer extends AnrTimer<BroadcastProcessQueue> {
+        BroadcastAnrTimer(Handler handler) {
+            super(Objects.requireNonNull(handler),
+                    MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT", true);
+        }
+
+        void start(@NonNull BroadcastProcessQueue queue, long timeoutMillis) {
+            start(queue, queue.app.getPid(), queue.app.uid, timeoutMillis);
+        }
+    }
+
     @Override
     public boolean finishReceiverLocked(@NonNull ProcessRecord app, int resultCode,
             @Nullable String resultData, @Nullable Bundle resultExtras, boolean resultAbort,
@@ -1292,14 +1294,16 @@
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             r.anrCount++;
             if (app != null && !app.isDebugging()) {
+                mAnrTimer.accept(queue);
                 final String packageName = getReceiverPackageName(receiver);
                 final String className = getReceiverClassName(receiver);
                 mService.appNotResponding(queue.app,
                         TimeoutRecord.forBroadcastReceiver(r.intent, packageName, className));
+            } else {
+                mAnrTimer.discard(queue);
             }
-        } else {
-            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
-            mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
+        } else if (queue.timeoutScheduled()) {
+            mAnrTimer.cancel(queue);
         }
 
         // Given that a receiver just finished, check if the "waitingFor" conditions are met.
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 60af280..e08fdd6 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -1437,6 +1437,7 @@
                 }
             });
             new MediaMetrics.Item(mMetricsId + "disconnectA2dp")
+                    .set(MediaMetrics.Property.EVENT, "disconnectA2dp")
                     .record();
             if (toRemove.size() > 0) {
                 final int delay = checkSendBecomingNoisyIntentInt(
@@ -1459,6 +1460,7 @@
                 }
             });
             new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink")
+                    .set(MediaMetrics.Property.EVENT, "disconnectA2dpSink")
                     .record();
             toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
         }
@@ -1474,6 +1476,7 @@
                 }
             });
             new MediaMetrics.Item(mMetricsId + "disconnectHearingAid")
+                    .set(MediaMetrics.Property.EVENT, "disconnectHearingAid")
                     .record();
             if (toRemove.size() > 0) {
                 final int delay = checkSendBecomingNoisyIntentInt(
@@ -1531,6 +1534,7 @@
                 }
             });
             new MediaMetrics.Item(mMetricsId + "disconnectLeAudio")
+                    .set(MediaMetrics.Property.EVENT, "disconnectLeAudio")
                     .record();
             if (toRemove.size() > 0) {
                 final int delay = checkSendBecomingNoisyIntentInt(device,
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 4bfc090..21273e0 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -17,10 +17,11 @@
 package com.android.server.display;
 
 import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
 import android.os.IBinder;
-import android.provider.DeviceConfigInterface;
 
-import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.brightness.clamper.HdrClamper;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.function.BooleanSupplier;
@@ -31,23 +32,30 @@
     private final NormalBrightnessModeController mNormalBrightnessModeController =
             new NormalBrightnessModeController();
 
+    private final HdrClamper mHdrClamper;
+
     private final Runnable mModeChangeCallback;
     private final boolean mUseNbmController;
 
+    private final boolean mUseHdrClamper;
+
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
+            DisplayManagerFlags flags) {
         this(hbmController, modeChangeCallback, displayDeviceConfig,
-                new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
+                new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags);
     }
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
-            DeviceConfigParameterProvider configParameterProvider) {
+            HdrClamper hdrClamper, DisplayManagerFlags flags) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
-        mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+        mUseHdrClamper = false;
+        mUseNbmController = flags.isNbmControllerEnabled();
         mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
+        mHdrClamper = hdrClamper;
     }
 
     void dump(PrintWriter pw) {
@@ -55,7 +63,6 @@
         pw.println("  mUseNormalBrightnessController=" + mUseNbmController);
         mHbmController.dump(pw);
         mNormalBrightnessModeController.dump(pw);
-
     }
 
     void onAmbientLuxChange(float ambientLux) {
@@ -63,6 +70,9 @@
                 () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
                 () -> mHbmController.onAmbientLuxChange(ambientLux)
         );
+        if (mUseHdrClamper) {
+            mHdrClamper.onAmbientLuxChange(ambientLux);
+        }
     }
 
     float getNormalBrightnessMax() {
@@ -118,7 +128,8 @@
     }
 
     float getHdrBrightnessValue() {
-        return mHbmController.getHdrBrightnessValue();
+        float hdrBrightness =  mHbmController.getHdrBrightnessValue();
+        return Math.min(hdrBrightness, mHdrClamper.getMaxBrightness());
     }
 
     float getTransitionPoint() {
@@ -138,4 +149,8 @@
             hbmChangesFunc.run();
         }
     }
+
+    public float getHdrTransitionRate() {
+        return mHdrClamper.getTransitionRate();
+    }
 }
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index 2d763bc..cd867f6 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -43,6 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal;
 
 import libcore.io.Streams;
 
@@ -573,8 +574,21 @@
     }
 
     private ScreenCapture.ScreenshotHardwareBuffer captureScreen() {
-        ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                mDisplayManagerInternal.systemScreenshot(mDisplayId);
+        WindowManagerInternal windowManagerService = LocalServices.getService(
+                WindowManagerInternal.class);
+        ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
+        ScreenCapture.SynchronousScreenCaptureListener screenCaptureListener =
+                ScreenCapture.createSyncCaptureListener();
+        ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+                .setCaptureSecureLayers(true)
+                .setAllowProtected(true)
+                .build();
+        try {
+            windowManagerService.captureDisplay(mDisplayId, captureArgs, screenCaptureListener);
+            screenshotBuffer = screenCaptureListener.getBuffer();
+        } catch (Exception e) {
+            screenshotBuffer = null;
+        }
         if (screenshotBuffer == null) {
             Slog.e(TAG, "Failed to take screenshot. Buffer is null");
             return null;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e3dafa4..3a6e5f93 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -53,6 +53,7 @@
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.HbmTiming;
+import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.HighBrightnessMode;
 import com.android.server.display.config.IntegerArray;
 import com.android.server.display.config.LuxThrottling;
@@ -232,7 +233,22 @@
  *          </point>
  *        </sdrHdrRatioMap>
  *      </highBrightnessMode>
- *
+ *      <hdrBrightnessConfig>
+ *         <brightnessMap>
+ *             <point>
+ *                <first>500</first>
+ *                <second>0.3</second>
+ *             </point>
+ *             <point>
+ *                 <first>1200</first>
+ *                 <second>0.6</second>
+ *             </point>
+ *         </brightnessMap>
+ *         <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
+ *         <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ *         <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
+ *         <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ *      </hdrBrightnessConfig>
  *      <luxThrottling>
  *        <brightnessLimitMap>
  *          <type>default</type>
@@ -769,6 +785,9 @@
     @Nullable
     private HostUsiVersion mHostUsiVersion;
 
+    @Nullable
+    private HdrBrightnessData mHdrBrightnessData;
+
     @VisibleForTesting
     DisplayDeviceConfig(Context context) {
         mContext = context;
@@ -1544,6 +1563,14 @@
     }
 
     /**
+     * @return HDR brightness related configuration
+     */
+    @Nullable
+    public HdrBrightnessData getHdrBrightnessData() {
+        return mHdrBrightnessData;
+    }
+
+    /**
      * @return Refresh rate range for specific profile id or null
      */
     @Nullable
@@ -1759,7 +1786,8 @@
                 + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
                 mScreenOffBrightnessSensorValueToLux)
                 + "\n"
-                + "mUsiVersion= " + mHostUsiVersion
+                + "mUsiVersion= " + mHostUsiVersion + "\n"
+                + "mHdrBrightnessData" + mHdrBrightnessData
                 + "}";
     }
 
@@ -1823,6 +1851,7 @@
                 loadRefreshRateSetting(config);
                 loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
                 loadUsiVersion(config);
+                mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
             } else {
                 Slog.w(TAG, "DisplayDeviceConfig file is null");
             }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 33ecaf1..df45001 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -137,7 +137,6 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.RefreshRateRange;
 import android.window.DisplayWindowPolicyController;
-import android.window.ScreenCapture;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -2674,42 +2673,6 @@
         return null;
     }
 
-    private ScreenCapture.ScreenshotHardwareBuffer systemScreenshotInternal(int displayId) {
-        final ScreenCapture.DisplayCaptureArgs captureArgs;
-        synchronized (mSyncRoot) {
-            final IBinder token = getDisplayToken(displayId);
-            if (token == null) {
-                return null;
-            }
-            final LogicalDisplay logicalDisplay = mLogicalDisplayMapper.getDisplayLocked(displayId);
-            if (logicalDisplay == null) {
-                return null;
-            }
-
-            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token)
-                    .setSize(displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight())
-                    .setCaptureSecureLayers(true)
-                    .setAllowProtected(true)
-                    .build();
-        }
-        return ScreenCapture.captureDisplay(captureArgs);
-    }
-
-    private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) {
-        synchronized (mSyncRoot) {
-            final IBinder token = getDisplayToken(displayId);
-            if (token == null) {
-                return null;
-            }
-
-            final ScreenCapture.DisplayCaptureArgs captureArgs =
-                    new ScreenCapture.DisplayCaptureArgs.Builder(token)
-                            .build();
-            return ScreenCapture.captureDisplay(captureArgs);
-        }
-    }
-
     @VisibleForTesting
     DisplayedContentSamplingAttributes getDisplayedContentSamplingAttributesInternal(
             int displayId) {
@@ -3274,12 +3237,12 @@
             displayPowerController = new DisplayPowerController2(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
-                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
+                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
         } else {
             displayPowerController = new DisplayPowerController(
                     mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
                     mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
-                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
+                    () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
         }
         mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
         return displayPowerController;
@@ -4478,16 +4441,6 @@
         }
 
         @Override
-        public ScreenCapture.ScreenshotHardwareBuffer systemScreenshot(int displayId) {
-            return systemScreenshotInternal(displayId);
-        }
-
-        @Override
-        public ScreenCapture.ScreenshotHardwareBuffer userScreenshot(int displayId) {
-            return userScreenshotInternal(displayId);
-        }
-
-        @Override
         public DisplayInfo getDisplayInfo(int displayId) {
             return getDisplayInfoInternal(displayId, Process.myUid());
         }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 40dbabf..320684f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -75,6 +75,7 @@
 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.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.utils.SensorUtils;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -592,7 +593,7 @@
             SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
             BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
             Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
-            boolean bootCompleted) {
+            boolean bootCompleted, DisplayManagerFlags flags) {
 
         mInjector = injector != null ? injector : new Injector();
         mClock = mInjector.getClock();
@@ -677,7 +678,7 @@
         HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
 
         mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig);
+                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
 
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
@@ -1921,8 +1922,25 @@
             if (isValidBrightnessValue(animateValue)
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
-                if (initialRampSkip || hasBrightnessBuckets
-                        || !isDisplayContentVisible || brightnessIsTemporary) {
+                boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
+                        || !isDisplayContentVisible || brightnessIsTemporary;
+                if (!skipAnimation && BrightnessSynchronizer.floatEquals(
+                        sdrAnimateValue, currentSdrBrightness)) {
+                    // Going from HDR to no HDR; visually this should be a "no-op" anyway
+                    // as the remaining SDR content's brightness should be holding steady
+                    // due to the sdr brightness not shifting
+                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
+                        skipAnimation = true;
+                    }
+
+                    // Going from no HDR to HDR; visually this is a significant scene change
+                    // and the animation just prevents advanced clients from doing their own
+                    // handling of enter/exit animations if they would like to do such a thing
+                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
+                        skipAnimation = true;
+                    }
+                }
+                if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
                 } else {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 460c351..597738e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -77,6 +77,7 @@
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.state.DisplayStateController;
 import com.android.server.display.utils.SensorUtils;
@@ -472,7 +473,7 @@
             SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
             BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
             Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
-            boolean bootCompleted) {
+            boolean bootCompleted, DisplayManagerFlags flags) {
 
         mInjector = injector != null ? injector : new Injector();
         mClock = mInjector.getClock();
@@ -539,8 +540,8 @@
                 modeChangeCallback);
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
-        mBrightnessRangeController = new BrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig);
+        mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
+                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -1497,6 +1498,9 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
+            // custom transition duration
+            float customTransitionRate = -1f;
+
             // 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.
@@ -1511,6 +1515,7 @@
                 // We want to scale HDR brightness level with the SDR level, we also need to restore
                 // SDR brightness immediately when entering dim or low power mode.
                 animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+                customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1519,10 +1524,30 @@
             if (BrightnessUtils.isValidBrightnessValue(animateValue)
                     && (animateValue != currentBrightness
                     || sdrAnimateValue != currentSdrBrightness)) {
-                if (initialRampSkip || hasBrightnessBuckets
-                        || !isDisplayContentVisible || brightnessIsTemporary) {
+                boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
+                        || !isDisplayContentVisible || brightnessIsTemporary;
+                if (!skipAnimation && BrightnessSynchronizer.floatEquals(
+                        sdrAnimateValue, currentSdrBrightness)) {
+                    // Going from HDR to no HDR; visually this should be a "no-op" anyway
+                    // as the remaining SDR content's brightness should be holding steady
+                    // due to the sdr brightness not shifting
+                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
+                        skipAnimation = true;
+                    }
+
+                    // Going from no HDR to HDR; visually this is a significant scene change
+                    // and the animation just prevents advanced clients from doing their own
+                    // handling of enter/exit animations if they would like to do such a thing
+                    if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
+                        skipAnimation = true;
+                    }
+                }
+                if (skipAnimation) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
                             SCREEN_ANIMATION_RATE_MINIMUM);
+                } else if (customTransitionRate > 0) {
+                    animateScreenBrightness(animateValue, sdrAnimateValue,
+                            customTransitionRate);
                 } else {
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
@@ -2968,6 +2993,14 @@
                     hbmChangeCallback, hbmMetadata, context);
         }
 
+        BrightnessRangeController getBrightnessRangeController(
+                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags) {
+            return new BrightnessRangeController(hbmController,
+                    modeChangeCallback, displayDeviceConfig, handler, flags);
+        }
+
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 52b92c4..378cdba 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -31,7 +31,12 @@
     private final FloatProperty<T> mProperty;
 
     private float mCurrentValue;
-    private float mTargetValue;
+
+    // target in HLG space
+    private float mTargetHlgValue;
+
+    // target in linear space
+    private float mTargetLinearValue;
     private float mRate;
     private float mAnimationIncreaseMaxTimeSecs;
     private float mAnimationDecreaseMaxTimeSecs;
@@ -78,7 +83,8 @@
             if (mFirstTime || target != mCurrentValue) {
                 mFirstTime = false;
                 mRate = 0;
-                mTargetValue = target;
+                mTargetHlgValue = target;
+                mTargetLinearValue = targetLinear;
                 mCurrentValue = target;
                 setPropertyValue(target);
                 mAnimating = false;
@@ -105,13 +111,14 @@
         // Otherwise, continue at the previous rate.
         if (!mAnimating
                 || rate > mRate
-                || (target <= mCurrentValue && mCurrentValue <= mTargetValue)
-                || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) {
+                || (target <= mCurrentValue && mCurrentValue <= mTargetHlgValue)
+                || (mTargetHlgValue <= mCurrentValue && mCurrentValue <= target)) {
             mRate = rate;
         }
 
-        final boolean changed = (mTargetValue != target);
-        mTargetValue = target;
+        final boolean changed = (mTargetHlgValue != target);
+        mTargetHlgValue = target;
+        mTargetLinearValue = targetLinear;
 
         // Start animating.
         if (!mAnimating && target != mCurrentValue) {
@@ -135,7 +142,11 @@
      * into linear space.
      */
     private void setPropertyValue(float val) {
-        final float linearVal = BrightnessUtils.convertGammaToLinear(val);
+        // To avoid linearVal inconsistency when converting to HLG and back to linear space
+        // used original target linear value for final animation step
+        float linearVal =
+                val == mTargetHlgValue ? mTargetLinearValue : BrightnessUtils.convertGammaToLinear(
+                        val);
         mProperty.setValue(mObject, linearVal);
     }
 
@@ -150,13 +161,13 @@
         final float scale = ValueAnimator.getDurationScale();
         if (scale == 0) {
             // Animation off.
-            mAnimatedValue = mTargetValue;
+            mAnimatedValue = mTargetHlgValue;
         } else {
             final float amount = timeDelta * mRate / scale;
-            if (mTargetValue > mCurrentValue) {
-                mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue);
+            if (mTargetHlgValue > mCurrentValue) {
+                mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetHlgValue);
             } else {
-                mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue);
+                mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetHlgValue);
             }
         }
         final float oldCurrentValue = mCurrentValue;
@@ -164,7 +175,7 @@
         if (oldCurrentValue != mCurrentValue) {
             setPropertyValue(mCurrentValue);
         }
-        if (mTargetValue == mCurrentValue) {
+        if (mTargetHlgValue == mCurrentValue) {
             mAnimating = false;
         }
     }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6936112..d910e16 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -167,6 +167,8 @@
             int width, int height, int densityDpi) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
         if (device != null) {
+            Slog.v(TAG, "Resize VirtualDisplay " + device.mName + " to " + width
+                    + " " + height);
             device.resizeLocked(width, height, densityDpi);
         }
     }
@@ -183,6 +185,7 @@
     public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
         if (device != null) {
+            Slog.v(TAG, "Update surface for VirtualDisplay " + device.mName);
             device.setSurfaceLocked(surface);
         }
     }
@@ -197,6 +200,7 @@
     public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
         VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
         if (device != null) {
+            Slog.v(TAG, "Release VirtualDisplay " + device.mName);
             device.destroyLocked(true);
             appToken.unlinkToDeath(device, 0);
         }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
new file mode 100644
index 0000000..079a196
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 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.brightness.clamper;
+
+import android.os.Handler;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HdrClamper {
+
+    private final Configuration mConfiguration = new Configuration();
+
+    private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
+
+    private final Handler mHandler;
+
+    private final Runnable mDebouncer;
+
+    private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+    // brightness change speed, in units per seconds,
+    private float mTransitionRate = -1f;
+
+    private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+    private float mDesiredTransitionDuration = -1; // in seconds
+
+    public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+            Handler handler) {
+        mClamperChangeListener = clamperChangeListener;
+        mHandler = handler;
+        mDebouncer = () -> {
+            mTransitionRate = Math.abs((mMaxBrightness - mDesiredMaxBrightness)
+                    / mDesiredTransitionDuration);
+            mMaxBrightness = mDesiredMaxBrightness;
+            mClamperChangeListener.onChanged();
+        };
+    }
+
+    // Called in same looper: mHandler.getLooper()
+    public float getMaxBrightness() {
+        return mMaxBrightness;
+    }
+
+    // Called in same looper: mHandler.getLooper()
+    public float getTransitionRate() {
+        return mTransitionRate;
+    }
+
+
+    /**
+     * Updates brightness cap in response to ambient lux change.
+     * Called by ABC in same looper: mHandler.getLooper()
+     */
+    public void onAmbientLuxChange(float ambientLux) {
+        float expectedMaxBrightness = findBrightnessLimit(ambientLux);
+        if (mMaxBrightness == expectedMaxBrightness) {
+            mDesiredMaxBrightness = mMaxBrightness;
+            mDesiredTransitionDuration = -1;
+            mTransitionRate = -1f;
+            mHandler.removeCallbacks(mDebouncer);
+        } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
+            mDesiredMaxBrightness = expectedMaxBrightness;
+            long debounceTime;
+            if (mDesiredMaxBrightness > mMaxBrightness) {
+                debounceTime = mConfiguration.mIncreaseConfig.mDebounceTimeMillis;
+                mDesiredTransitionDuration =
+                        (float) mConfiguration.mIncreaseConfig.mTransitionTimeMillis / 1000;
+            } else {
+                debounceTime = mConfiguration.mDecreaseConfig.mDebounceTimeMillis;
+                mDesiredTransitionDuration =
+                        (float) mConfiguration.mDecreaseConfig.mTransitionTimeMillis / 1000;
+            }
+
+            mHandler.removeCallbacks(mDebouncer);
+            mHandler.postDelayed(mDebouncer, debounceTime);
+        }
+    }
+
+    @VisibleForTesting
+    Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    private float findBrightnessLimit(float ambientLux) {
+        float foundAmbientBoundary = Float.MAX_VALUE;
+        float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+        for (Map.Entry<Float, Float> brightnessPoint :
+                mConfiguration.mMaxBrightnessLimits.entrySet()) {
+            float ambientBoundary = brightnessPoint.getKey();
+            // find ambient lux upper boundary closest to current ambient lux
+            if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
+                foundMaxBrightness = brightnessPoint.getValue();
+                foundAmbientBoundary = ambientBoundary;
+            }
+        }
+        return foundMaxBrightness;
+    }
+
+    @VisibleForTesting
+    static class Configuration {
+        final Map<Float, Float> mMaxBrightnessLimits = new HashMap<>();
+        final TransitionConfiguration mIncreaseConfig = new TransitionConfiguration();
+
+        final TransitionConfiguration mDecreaseConfig = new TransitionConfiguration();
+    }
+
+    @VisibleForTesting
+    static class TransitionConfiguration {
+        long mDebounceTimeMillis;
+
+        long mTransitionTimeMillis;
+    }
+}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
new file mode 100644
index 0000000..06d3c5b
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 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.config;
+
+import android.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Brightness config for HDR content
+ */
+public class HdrBrightnessData {
+
+    /**
+     * Lux to brightness map
+     */
+    public final Map<Float, Float> mMaxBrightnessLimits;
+
+    /**
+     * Debounce time for brightness increase
+     */
+    public final long mBrightnessIncreaseDebounceMillis;
+
+    /**
+     * Brightness increase animation duration
+     */
+    public final long mBrightnessIncreaseDurationMillis;
+
+    /**
+     * Debounce time for brightness decrease
+     */
+    public final long mBrightnessDecreaseDebounceMillis;
+
+    /**
+     * Brightness decrease animation duration
+     */
+    public final long mBrightnessDecreaseDurationMillis;
+
+    private HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
+            long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
+            long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+        mMaxBrightnessLimits = maxBrightnessLimits;
+        mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
+        mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+        mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
+        mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+    }
+
+    @Override
+    public String toString() {
+        return "HdrBrightnessData {"
+                + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
+                + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
+                + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+                + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
+                + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+                + "} ";
+    }
+
+    /**
+     * Loads HdrBrightnessData from DisplayConfiguration
+     */
+    @Nullable
+    public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
+        HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
+        if (hdrConfig == null) {
+            return null;
+        }
+
+        List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
+        Map<Float, Float> brightnessLimits = new HashMap<>();
+        for (NonNegativeFloatToFloatPoint point: points) {
+            brightnessLimits.put(point.getFirst().floatValue(), point.getSecond().floatValue());
+        }
+
+        return new HdrBrightnessData(brightnessLimits,
+                hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
+                hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+                hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
+                hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+    }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index fddac6d..aebd8a0 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -30,43 +30,68 @@
 public class DisplayManagerFlags {
     private static final boolean DEBUG = false;
     private static final String TAG = "DisplayManagerFlags";
-    private boolean mIsConnectedDisplayManagementEnabled = false;
-    private boolean mIsConnectedDisplayManagementEnabledSet = false;
 
-    private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
-        // TODO(b/299462337) Remove when the infrastructure is ready.
-        if ((Build.IS_ENG || Build.IS_USERDEBUG)
-                    && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
-            return true;
-        }
-        try {
-            return flagFunction.get();
-        } catch (Throwable ex) {
-            if (DEBUG) {
-                Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
-            }
-            return false;
-        }
-    }
+    private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
+            Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
+            Flags::enableConnectedDisplayManagement);
 
-    // TODO(b/297159910): Simplify using READ-ONLY flags when available.
+    private final FlagState mNbmControllerFlagState = new FlagState(
+            Flags.FLAG_ENABLE_NBM_CONTROLLER,
+            Flags::enableNbmController);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
-        if (mIsConnectedDisplayManagementEnabledSet) {
-            if (DEBUG) {
-                Slog.d(TAG, "isConnectedDisplayManagementEnabled. Recall = "
-                                    + mIsConnectedDisplayManagementEnabled);
+        return mConnectedDisplayManagementFlagState.isEnabled();
+    }
+
+    /** Returns whether hdr clamper is enabled on not*/
+    public boolean isNbmControllerEnabled() {
+        return mNbmControllerFlagState.isEnabled();
+    }
+
+    private static class FlagState {
+
+        private final String mName;
+
+        private final Supplier<Boolean> mFlagFunction;
+        private boolean mEnabledSet;
+        private boolean mEnabled;
+
+        private FlagState(String name, Supplier<Boolean> flagFunction) {
+            mName = name;
+            mFlagFunction = flagFunction;
+        }
+
+        // TODO(b/297159910): Simplify using READ-ONLY flags when available.
+        private boolean isEnabled() {
+            if (mEnabledSet) {
+                if (DEBUG) {
+                    Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled);
+                }
+                return mEnabled;
             }
-            return mIsConnectedDisplayManagementEnabled;
+            mEnabled = flagOrSystemProperty(mFlagFunction, mName);
+            if (DEBUG) {
+                Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled);
+            }
+            mEnabledSet = true;
+            return mEnabled;
         }
-        mIsConnectedDisplayManagementEnabled =
-                flagOrSystemProperty(Flags::enableConnectedDisplayManagement,
-                        Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT);
-        if (DEBUG) {
-            Slog.d(TAG, "isConnectedDisplayManagementEnabled. Flag value = "
-                                + mIsConnectedDisplayManagementEnabled);
+
+        private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
+            // TODO(b/299462337) Remove when the infrastructure is ready.
+            if ((Build.IS_ENG || Build.IS_USERDEBUG)
+                    && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
+                return true;
+            }
+            try {
+                return flagFunction.get();
+            } catch (Throwable ex) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
+                }
+                return false;
+            }
         }
-        mIsConnectedDisplayManagementEnabledSet = true;
-        return mIsConnectedDisplayManagementEnabled;
     }
 }
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 2c3c66e..12306b0 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,3 +9,11 @@
     bug: "280739508"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_nbm_controller"
+    namespace: "display_manager"
+    description: "Feature flag for Normal Brightness Mode Controller"
+    bug: "277877297"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index a2c8748..2ede56d 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -62,10 +62,10 @@
         mWindowHandle.ownerUid = uid;
         mWindowHandle.scaleFactor = 1.0f;
         mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
-        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SPY;
+        mWindowHandle.inputConfig =
+                InputConfig.NOT_FOCUSABLE | InputConfig.SPY | InputConfig.TRUSTED_OVERLAY;
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
         t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 62660c4..6b399de 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -95,6 +95,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.PointerIcon;
 import android.view.Surface;
@@ -682,6 +683,12 @@
         return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
     }
 
+    @Override // Binder call
+    public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) {
+        Objects.requireNonNull(layoutDescriptor, "layoutDescriptor must not be null");
+        return mKeyboardLayoutManager.getKeyCharacterMap(layoutDescriptor);
+    }
+
     /**
      * Transfer the current touch gesture to the provided window.
      *
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index a5162c0..0eb620f 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -63,6 +63,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InputDevice;
+import android.view.KeyCharacterMap;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
@@ -430,6 +431,23 @@
         return result[0];
     }
 
+    @AnyThread
+    public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) {
+        final String[] overlay = new String[1];
+        visitKeyboardLayout(layoutDescriptor,
+                (resources, keyboardLayoutResId, layout) -> {
+                    try (InputStreamReader stream = new InputStreamReader(
+                            resources.openRawResource(keyboardLayoutResId))) {
+                        overlay[0] = Streams.readFully(stream);
+                    } catch (IOException | Resources.NotFoundException ignored) {
+                    }
+                });
+        if (TextUtils.isEmpty(overlay[0])) {
+            return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+        }
+        return KeyCharacterMap.load(layoutDescriptor, overlay[0]);
+    }
+
     private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
         final PackageManager pm = mContext.getPackageManager();
         Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index dbbbed3..7726f40 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -57,13 +57,13 @@
                 InputConfig.NOT_FOCUSABLE
                         | InputConfig.NOT_TOUCHABLE
                         | InputConfig.SPY
-                        | InputConfig.INTERCEPTS_STYLUS;
+                        | InputConfig.INTERCEPTS_STYLUS
+                        | InputConfig.TRUSTED_OVERLAY;
 
         // Configure the surface to receive stylus events across the entire display.
         mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
         t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 699e9c8..032778c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -116,7 +116,6 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.IWindowManager;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -124,7 +123,6 @@
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.view.WindowManagerGlobal;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
@@ -2989,9 +2987,13 @@
             ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
                     "Waiting for the lazy init of mImeDrawsImeNavBarRes");
         }
+        // Whether the current display has a navigation bar. When this is false (e.g. emulator),
+        // the IME should not draw the IME navigation bar.
+        final boolean hasNavigationBar = mWindowManagerInternal
+                .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
+                        ? mCurTokenDisplayId : DEFAULT_DISPLAY);
         final boolean canImeDrawsImeNavBar =
-                mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get()
-                        && hasNavigationBarOnCurrentDisplay();
+                mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
         final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
                 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
         return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2999,21 +3001,6 @@
                 ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0);
     }
 
-    /**
-     * Whether the current display has a navigation bar. When this is {@code false} (e.g. emulator),
-     * the IME should <em>not</em> draw the IME navigation bar.
-     */
-    @GuardedBy("ImfLock.class")
-    private boolean hasNavigationBarOnCurrentDisplay() {
-        final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
-        try {
-            return wm.hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
-                    ? mCurTokenDisplayId : DEFAULT_DISPLAY);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     @GuardedBy("ImfLock.class")
     private boolean shouldShowImeSwitcherLocked(int visibility) {
         if (!mShowOngoingImeSwitcherForPhones) return false;
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 0cfdaf2..10b6052 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -195,8 +195,12 @@
         mApplicationKeyStorage = applicationKeyStorage;
         mTestCertHelper = testOnlyInsecureCertificateHelper;
         mCleanupManager = cleanupManager;
-        // Clears data for removed users.
-        mCleanupManager.verifyKnownUsers();
+        try {
+            // Clears data for removed users.
+            mCleanupManager.verifyKnownUsers();
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to verify known users", e);
+        }
         try {
             mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mDatabase);
         } catch (NoSuchAlgorithmException e) {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index 66985e0..ddeeacc 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -24,6 +24,8 @@
 import android.media.MediaRoute2Info;
 import android.os.UserHandle;
 
+import com.android.media.flags.Flags;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -53,15 +55,10 @@
             return new NoOpBluetoothRouteController();
         }
 
-        MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
-        boolean isUsingLegacyController = flagManager.getBoolean(
-                MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
-                true);
-
-        if (isUsingLegacyController) {
-            return new LegacyBluetoothRouteController(context, btAdapter, listener);
-        } else {
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
             return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
+        } else {
+            return new LegacyBluetoothRouteController(context, btAdapter, listener);
         }
     }
 
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 3875c84..e17f4a3 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -25,6 +25,8 @@
 import android.media.MediaRoute2Info;
 import android.os.ServiceManager;
 
+import com.android.media.flags.Flags;
+
 /**
  * Controls device routes.
  *
@@ -44,18 +46,13 @@
         IAudioService audioService = IAudioService.Stub.asInterface(
                 ServiceManager.getService(Context.AUDIO_SERVICE));
 
-        MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
-        boolean isUsingLegacyController = flagManager.getBoolean(
-                MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
-                true);
-
-        if (isUsingLegacyController) {
-            return new LegacyDeviceRouteController(context,
+        if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+            return new AudioPoliciesDeviceRouteController(context,
                     audioManager,
                     audioService,
                     onDeviceRouteChangedListener);
         } else {
-            return new AudioPoliciesDeviceRouteController(context,
+            return new LegacyDeviceRouteController(context,
                     audioManager,
                     audioService,
                     onDeviceRouteChangedListener);
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index e31a7fc..1980403 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -508,7 +508,11 @@
                 case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
                     clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
                     if (device != null) {
-                        addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
+                        if (DEBUG) {
+                            Log.d(TAG, "Setting active a2dp devices. device=" + device);
+                        }
+
+                        addActiveDevices(device);
                     }
                     notifyBluetoothRoutesUpdated();
                     break;
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
index f555505..f90f64a 100644
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
@@ -36,7 +36,6 @@
     @StringDef(
             prefix = "FEATURE_",
             value = {
-                FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
                 FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE
             })
     @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@@ -44,14 +43,6 @@
     /* package */ @interface MediaFeatureFlag {}
 
     /**
-     * Whether to use old legacy implementation of BluetoothRouteController or new
-     * 'Audio Strategies'-aware controller.
-     */
-    /* package */ static final @MediaFeatureFlag String
-            FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER =
-            "BluetoothRouteController__enable_legacy_bluetooth_routes_controller";
-
-    /**
      * Whether to use IMPORTANCE_FOREGROUND (i.e. 100) or IMPORTANCE_FOREGROUND_SERVICE (i.e. 125)
      * as the minimum package importance for scanning.
      */
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 69e4a5b..13d1662 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -932,6 +932,7 @@
             if (callback == null) {
                 throw new IllegalArgumentException("callback must not be null");
             }
+            Slog.v(TAG, "Start the token instance " + this);
             // Cache result of calling into ActivityManagerService outside of the lock, to prevent
             // deadlock with WindowManagerService.
             final boolean hasFGS = mActivityManagerInternal.hasRunningForegroundService(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6a5b9d8..bada816 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2826,9 +2826,6 @@
             mAssistants.onBootPhaseAppsCanStart();
             mConditionProviders.onBootPhaseAppsCanStart();
             mHistoryManager.onBootPhaseAppsCanStart();
-            if (expireBitmaps()) {
-                NotificationBitmapJobService.scheduleJob(getContext());
-            }
             registerDeviceConfigChange();
             migrateDefaultNAS();
             maybeShowInitialReviewPermissionsNotification();
@@ -2837,6 +2834,10 @@
         } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
             mPreferencesHelper.updateFixedImportance(mUm.getUsers());
             mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            if (expireBitmaps()) {
+                NotificationBitmapJobService.scheduleJob(getContext());
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index b015a72..d2e980b 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -39,6 +39,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.util.FrameworkStatsLog;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -497,6 +498,7 @@
         final boolean is_non_dismissible;
         final int fsi_state;
         final boolean is_locked;
+        final int age_in_minutes;
         @DurationMillisLong long post_duration_millis; // Not final; calculated at the end.
 
         NotificationReported(NotificationRecordPair p,
@@ -541,6 +543,9 @@
                     hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
 
             this.is_locked = p.r.isLocked();
+
+            this.age_in_minutes = NotificationRecordLogger.getAgeInMinutes(
+                    p.r.getSbn().getPostTime(), p.r.getSbn().getNotification().when);
         }
     }
 
@@ -601,4 +606,13 @@
         }
         return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI;
     }
+
+    /**
+     * @param postTimeMs time (in {@link System#currentTimeMillis} time) the notification was posted
+     * @param whenMs A timestamp related to this notification, in milliseconds since the epoch.
+     * @return difference in duration as an integer in minutes
+     */
+    static int getAgeInMinutes(long postTimeMs, long whenMs) {
+        return (int) Duration.ofMillis(postTimeMs - whenMs).toMinutes();
+    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 9da0e98..fc0a776 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -77,7 +77,8 @@
                 notificationReported.is_non_dismissible,
                 notificationReported.post_duration_millis,
                 notificationReported.fsi_state,
-                notificationReported.is_locked);
+                notificationReported.is_locked,
+                notificationReported.age_in_minutes);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a700d32..71562dc 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1022,6 +1022,8 @@
     @VisibleForTesting
     protected void setZenModeSetting(int zen) {
         Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+        ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
+                "updated setting");
         showZenUpgradeNotification(zen);
     }
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ffa2af1..316c4ac 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1024,7 +1024,7 @@
         if ("android".equals(packageName) || "system".equals(packageName)) {
             return androidApplication();
         }
-        if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+        if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
             // Already generates the external package name
             return generateApplicationInfoFromSettings(packageName,
                     flags, filterCallingUid, userId);
@@ -1518,7 +1518,6 @@
             pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null;
             pi.firstInstallTime = state.getFirstInstallTimeMillis();
             pi.lastUpdateTime = ps.getLastUpdateTime();
-            pi.isArchived = isArchived(state);
 
             ApplicationInfo ai = new ApplicationInfo();
             ai.packageName = ps.getPackageName();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2ee0706..e1e5e6d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -44,6 +44,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+
 import static com.android.server.pm.DexOptHelper.useArtService;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -111,6 +112,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.DataLoaderType;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfoLite;
@@ -1142,15 +1144,18 @@
 
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final ParsedPackage parsedPackage;
+        final ArchivedPackageParcel archivedPackage;
         try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
             if (request.getPackageLite() == null || !request.isArchived()) {
                 // TODO: pass packageLite from install request instead of reparsing the package
                 parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
                 AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+                archivedPackage = null;
             } else {
                 // Archived install mode, no APK.
                 parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(),
                         parseFlags);
+                archivedPackage = request.getPackageLite().getArchivedPackage();
             }
         } catch (PackageManagerException e) {
             throw new PrepareFailure("Failed parse during installPackageLI", e);
@@ -1833,7 +1838,7 @@
             shouldCloseFreezerBeforeReturn = false;
 
             request.setPrepareResult(replace, targetScanFlags, targetParseFlags,
-                    oldPackageState, parsedPackage,
+                    oldPackageState, parsedPackage, archivedPackage,
                     replace /* clearCodeCache */, sysPkg, ps, disabledPs);
         } finally {
             request.setFreezer(freezer);
@@ -2164,6 +2169,9 @@
                 }
             }
             if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+                mPm.createArchiveStateIfNeeded(ps,
+                        installRequest.getArchivedPackage(),
+                        installRequest.getNewUsers());
                 mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
                 mPm.updateInstantAppInstallerLocked(packageName);
             }
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 3a6d423..a4ee3c8 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -21,6 +21,9 @@
 import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.os.Process.INVALID_UID;
+
+import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
 import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
@@ -29,6 +32,7 @@
 import android.annotation.Nullable;
 import android.apex.ApexInfo;
 import android.app.AppOpsManager;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.DataLoaderType;
 import android.content.pm.IPackageInstallObserver2;
 import android.content.pm.PackageInstaller;
@@ -55,6 +59,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashSet;
 import java.util.List;
 
 final class InstallRequest {
@@ -77,6 +82,8 @@
     /** parsed package to be scanned */
     @Nullable
     private ParsedPackage mParsedPackage;
+    @Nullable
+    private ArchivedPackageParcel mArchivedPackage;
     private boolean mClearCodeCache;
     private boolean mSystem;
     @Nullable
@@ -144,6 +151,9 @@
     @NonNull
     private int[] mUpdateBroadcastInstantUserIds = EMPTY_INT_ARRAY;
 
+    @NonNull
+    private ArrayList<String> mWarnings = new ArrayList<>();
+
     // New install
     InstallRequest(InstallingSession params) {
         mUserId = params.getUser().getIdentifier();
@@ -189,6 +199,7 @@
         }
         mInstallArgs = null;
         mParsedPackage = parsedPackage;
+        mArchivedPackage = null;
         mParseFlags = parseFlags;
         mScanFlags = scanFlags;
         mScanResult = scanResult;
@@ -448,6 +459,9 @@
         return mParsedPackage;
     }
 
+    @Nullable
+    public ArchivedPackageParcel getArchivedPackage() { return mArchivedPackage; }
+
     @ParsingPackageUtils.ParseFlags
     public int getParseFlags() {
         return mParseFlags;
@@ -650,6 +664,11 @@
         return mUpdateBroadcastInstantUserIds;
     }
 
+    @NonNull
+    public ArrayList<String> getWarnings() {
+        return mWarnings;
+    }
+
     public void setScanFlags(int scanFlags) {
         mScanFlags = scanFlags;
     }
@@ -753,7 +772,8 @@
 
     public void setPrepareResult(boolean replace, int scanFlags,
             int parseFlags, PackageState existingPackageState,
-            ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
+            ParsedPackage packageToScan, ArchivedPackageParcel archivedPackage,
+            boolean clearCodeCache, boolean system,
             PackageSetting originalPs, PackageSetting disabledPs) {
         mReplace = replace;
         mScanFlags = scanFlags;
@@ -761,6 +781,7 @@
         mExistingPackageName =
                 existingPackageState != null ? existingPackageState.getPackageName() : null;
         mParsedPackage = packageToScan;
+        mArchivedPackage = archivedPackage;
         mClearCodeCache = clearCodeCache;
         mSystem = system;
         mOriginalPs = originalPs;
@@ -845,6 +866,10 @@
         }
     }
 
+    public void addWarning(@NonNull String warning) {
+        mWarnings.add(warning);
+    }
+
     public void onPrepareStarted() {
         if (mPackageMetrics != null) {
             mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
@@ -894,22 +919,37 @@
     }
 
     public void onDexoptFinished(DexoptResult dexoptResult) {
-        if (mPackageMetrics == null) {
-            return;
-        }
-        mDexoptStatus = dexoptResult.getFinalStatus();
-        if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) {
-            return;
-        }
-        long durationMillis = 0;
-        for (DexoptResult.PackageDexoptResult packageResult :
-                dexoptResult.getPackageDexoptResults()) {
-            for (DexoptResult.DexContainerFileDexoptResult fileResult :
-                    packageResult.getDexContainerFileDexoptResults()) {
-                durationMillis += fileResult.getDex2oatWallTimeMillis();
+        // Only report external profile warnings when installing from adb. The goal is to warn app
+        // developers if they have provided bad external profiles, so it's not beneficial to report
+        // those warnings in the normal app install workflow.
+        if (isInstallFromAdb()) {
+            var externalProfileErrors = new LinkedHashSet<String>();
+            for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) {
+                for (DexContainerFileDexoptResult fileResult :
+                        packageResult.getDexContainerFileDexoptResults()) {
+                    externalProfileErrors.addAll(fileResult.getExternalProfileErrors());
+                }
+            }
+            if (!externalProfileErrors.isEmpty()) {
+                addWarning("Error occurred during dexopt when processing external profiles:\n  "
+                        + String.join("\n  ", externalProfileErrors));
             }
         }
-        mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis);
+
+        // Report dexopt metrics.
+        if (mPackageMetrics != null) {
+            mDexoptStatus = dexoptResult.getFinalStatus();
+            if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) {
+                long durationMillis = 0;
+                for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) {
+                    for (DexContainerFileDexoptResult fileResult :
+                            packageResult.getDexContainerFileDexoptResults()) {
+                        durationMillis += fileResult.getDex2oatWallTimeMillis();
+                    }
+                }
+                mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis);
+            }
+        }
     }
 
     public void onInstallCompleted() {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 64cdca3..e8be748 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,12 +31,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageParcel;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
@@ -56,6 +59,7 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserStateInternal;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -158,7 +162,8 @@
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
         verifyInstaller(responsibleInstallerPackage);
 
-        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
+                userId);
         final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
         mPm.mHandler.post(() -> {
             try {
@@ -172,13 +177,34 @@
         return archiveState;
     }
 
-    private ArchiveState createArchiveStateInternal(String packageName, int userId,
+    static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
+            int userId, String installerPackage) {
+        try {
+            var packageName = archivedPackage.packageName;
+            var mainActivities = archivedPackage.archivedActivities;
+            List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
+            for (int i = 0, size = mainActivities.length; i < size; ++i) {
+                var mainActivity = mainActivities[i];
+                Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+                ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+                        mainActivity.title, iconPath, null);
+                archiveActivityInfos.add(activityInfo);
+            }
+
+            return new ArchiveState(archiveActivityInfos, installerPackage);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to create archive state", e);
+            return null;
+        }
+    }
+
+    ArchiveState createArchiveStateInternal(String packageName, int userId,
             List<LauncherActivityInfo> mainActivities, String installerPackage)
             throws IOException {
-        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
-        for (int i = 0; i < mainActivities.size(); i++) {
+        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+        for (int i = 0, size = mainActivities.size(); i < size; i++) {
             LauncherActivityInfo mainActivity = mainActivities.get(i);
-            Path iconPath = storeIcon(packageName, mainActivity, userId);
+            Path iconPath = storeIcon(packageName, mainActivity, userId, i);
             ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
                     mainActivity.getLabel().toString(), iconPath, null);
             archiveActivityInfos.add(activityInfo);
@@ -188,17 +214,30 @@
     }
 
     // TODO(b/298452477) Handle monochrome icons.
+    private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
+            @UserIdInt int userId, int index) throws IOException {
+        if (mainActivity.iconBitmap == null) {
+            return null;
+        }
+        File iconsDir = createIconsDir(userId);
+        File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
+        try (FileOutputStream out = new FileOutputStream(iconFile)) {
+            out.write(mainActivity.iconBitmap);
+            out.flush();
+        }
+        return iconFile.toPath();
+    }
+
     @VisibleForTesting
     Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
-            @UserIdInt int userId)
-            throws IOException {
+            @UserIdInt int userId, int index) throws IOException {
         int iconResourceId = mainActivity.getActivityInfo().getIconResource();
         if (iconResourceId == 0) {
             // The app doesn't define an icon. No need to store anything.
             return null;
         }
         File iconsDir = createIconsDir(userId);
-        File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png");
+        File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
         Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
         try (FileOutputStream out = new FileOutputStream(iconFile)) {
             // Note: Quality is ignored for PNGs.
@@ -228,6 +267,9 @@
      */
     public boolean verifySupportsUnarchival(String installerPackage) {
         // TODO(b/278553670) Check if installerPackage supports unarchival.
+        if (TextUtils.isEmpty(installerPackage)) {
+            return false;
+        }
         return true;
     }
 
@@ -265,6 +307,46 @@
         mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
     }
 
+    /**
+     * Returns the icon of an archived app. This is the icon of the main activity of the app.
+     *
+     * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
+     * launcher activities, only one of the icons is returned arbitrarily.
+     */
+    public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(user);
+
+        Computer snapshot = mPm.snapshotComputer();
+        int callingUid = Binder.getCallingUid();
+        int userId = user.getIdentifier();
+        PackageStateInternal ps;
+        try {
+            ps = getPackageState(packageName, snapshot, callingUid, userId);
+            snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
+                    "getArchivedAppIcon");
+            verifyArchived(ps, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
+
+        List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
+                userId).getArchiveState().getActivityInfos();
+        if (activityInfos.size() == 0) {
+            return null;
+        }
+
+        // TODO(b/298452477) Handle monochrome icons.
+        // In the rare case the archived app defined more than two launcher activities, we choose
+        // the first one arbitrarily.
+        return decodeIcon(activityInfos.get(0));
+    }
+
+    @VisibleForTesting
+    Bitmap decodeIcon(ArchiveActivityInfo archiveActivityInfo) {
+        return BitmapFactory.decodeFile(archiveActivityInfo.getIconBitmap().toString());
+    }
+
     private void verifyArchived(PackageStateInternal ps, int userId)
             throws PackageManager.NameNotFoundException {
         PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
@@ -310,16 +392,16 @@
                 /* initialExtras= */ null);
     }
 
-    private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+    List<LauncherActivityInfo> getLauncherActivityInfos(String packageName,
             int userId) throws PackageManager.NameNotFoundException {
         List<LauncherActivityInfo> mainActivities =
                 Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
-                        ps.getPackageName(),
+                        packageName,
                         new UserHandle(userId)));
         if (mainActivities.isEmpty()) {
             throw new PackageManager.NameNotFoundException(
                     TextUtils.formatSimple("The app %s does not have a main activity.",
-                            ps.getPackageName()));
+                            packageName));
         }
 
         return mainActivities;
@@ -340,7 +422,7 @@
         return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
     }
 
-    private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+    static String getResponsibleInstallerPackage(PackageStateInternal ps) {
         return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName)
                 ? ps.getInstallSource().mInstallerPackageName
                 : ps.getInstallSource().mUpdateOwnerPackageName;
@@ -423,7 +505,7 @@
         }
     }
 
-    private File createIconsDir(@UserIdInt int userId) throws IOException {
+    private static File createIconsDir(@UserIdInt int userId) throws IOException {
         File iconsDir = getIconsDir(userId);
         if (!iconsDir.isDirectory()) {
             iconsDir.delete();
@@ -436,7 +518,7 @@
         return iconsDir;
     }
 
-    private File getIconsDir(int userId) {
+    private static File getIconsDir(int userId) {
         return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
     }
 
@@ -462,4 +544,90 @@
         drawable.draw(canvas);
         return bitmap;
     }
+
+    private static byte[] bytesFromBitmapFile(Path path) throws IOException {
+        if (path == null) {
+            return null;
+        }
+        // Technically we could just read the bytes, but we want to be sure we store the
+        // right format.
+        return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
+    }
+
+    private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException {
+        if (bitmap == null) {
+            return null;
+        }
+
+        try (ByteArrayOutputStream baos = new ByteArrayOutputStream(
+                bitmap.getByteCount())) {
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+            return baos.toByteArray();
+        }
+    }
+
+    /**
+     * Creates serializable archived activities from existing ArchiveState.
+     */
+    static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState)
+            throws IOException {
+        var infos = archiveState.getActivityInfos();
+        if (infos == null || infos.isEmpty()) {
+            throw new IllegalArgumentException("No activities in archive state");
+        }
+
+        List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+        for (int i = 0, size = infos.size(); i < size; ++i) {
+            var info = infos.get(i);
+            if (info == null) {
+                continue;
+            }
+            var archivedActivity = new ArchivedActivityParcel();
+            archivedActivity.title = info.getTitle();
+            archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
+            archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
+                    info.getMonochromeIconBitmap());
+            activities.add(archivedActivity);
+        }
+
+        if (activities.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Failed to extract title and icon of main activities");
+        }
+
+        return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+    }
+
+    /**
+     * Creates serializable archived activities from launcher activities.
+     */
+    static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
+            throws IOException {
+        if (infos == null || infos.isEmpty()) {
+            throw new IllegalArgumentException("No launcher activities");
+        }
+
+        List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+        for (int i = 0, size = infos.size(); i < size; ++i) {
+            var info = infos.get(i);
+            if (info == null) {
+                continue;
+            }
+            var archivedActivity = new ArchivedActivityParcel();
+            archivedActivity.title = info.getLabel().toString();
+            archivedActivity.iconBitmap =
+                    info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
+                            drawableToBitmap(info.getIcon(/* density= */ 0)));
+            // TODO(b/298452477) Handle monochrome icons.
+            archivedActivity.monochromeIconBitmap = null;
+            activities.add(archivedActivity);
+        }
+
+        if (activities.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Failed to extract title and icon of main activities");
+        }
+
+        return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2e9da09..d699baa 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -40,6 +40,7 @@
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDONLY;
 import static android.system.OsConstants.O_WRONLY;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.util.XmlUtils.readBitmapAttribute;
 import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
@@ -1165,11 +1166,6 @@
                 throw new IllegalArgumentException(
                         "Archived installation can only use Streaming System DataLoader.");
             }
-            if (!TextUtils.isEmpty(params.appPackageName) && !isArchivedInstallationAllowed(
-                    params.appPackageName)) {
-                throw new IllegalArgumentException(
-                        "Archived installation of this package is not allowed.");
-            }
         }
     }
 
@@ -1548,6 +1544,10 @@
             }
 
             var archPkg = metadata.getArchivedPackage();
+            if (archPkg == null) {
+                throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+                        "Metadata does not contain ArchivedPackage: " + file);
+            }
             if (archPkg.packageName == null || archPkg.signingDetails == null) {
                 throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
                         "ArchivedPackage does not contain required info: " + file);
@@ -3395,8 +3395,7 @@
                         "Archived installation of this package is not allowed.");
             }
 
-            if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName)
-                    && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
+            if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
                     getInstallSource().mInstallerPackageName)) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_SESSION_INVALID,
@@ -5139,6 +5138,10 @@
             if (!TextUtils.isEmpty(existing)) {
                 fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing);
             }
+            ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS);
+            if (!ArrayUtils.isEmpty(warnings)) {
+                fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings);
+            }
         }
         try {
             final BroadcastOptions options = BroadcastOptions.makeBasic();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d23dcbc..700fae9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
 import static com.android.server.pm.DexOptHelper.useArtService;
@@ -116,11 +117,7 @@
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.overlay.OverlayPaths;
-import android.content.pm.parsing.ApkLite;
-import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
@@ -228,6 +225,7 @@
 import com.android.server.pm.permission.PermissionManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageUserState;
@@ -1435,9 +1433,98 @@
                 break;
             }
         }
+        if (!request.getWarnings().isEmpty()) {
+            extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings());
+        }
         return extras;
     }
 
+    ArchivedPackageParcel getArchivedPackageInternal(@NonNull String packageName, int userId) {
+        Objects.requireNonNull(packageName);
+        int binderUid = Binder.getCallingUid();
+
+        Computer snapshot = snapshotComputer();
+        snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+                "getArchivedPackage");
+
+        ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
+        archPkg.packageName = packageName;
+
+        ArchiveState archiveState;
+        synchronized (mLock) {
+            PackageSetting ps = mSettings.getPackageLPr(packageName);
+            if (ps == null) {
+                return null;
+            }
+            var psi = ps.getUserStateOrDefault(userId);
+            archiveState = psi.getArchiveState();
+            if (archiveState == null && !psi.isInstalled()) {
+                return null;
+            }
+
+            archPkg.signingDetails = ps.getSigningDetails();
+
+            long longVersionCode = ps.getVersionCode();
+            archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
+            archPkg.versionCode = (int) longVersionCode;
+
+            // TODO(b/297916136): extract target sdk version.
+            archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+
+            // These get translated in flags important for user data management.
+            archPkg.defaultToDeviceProtectedStorage = String.valueOf(
+                    ps.isDefaultToDeviceProtectedStorage());
+            archPkg.requestLegacyExternalStorage = String.valueOf(
+                    ps.isRequestLegacyExternalStorage());
+            archPkg.userDataFragile = String.valueOf(ps.isUserDataFragile());
+        }
+
+        try {
+            if (archiveState != null) {
+                archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+                        archiveState);
+            } else {
+                var mainActivities =
+                        mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
+                                userId);
+                archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+                        mainActivities);
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Package does not have a main activity", e);
+        }
+
+        return archPkg;
+    }
+
+    void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage,
+            int[] userIds) {
+        if (pkgSetting == null || archivePackage == null
+                || archivePackage.archivedActivities == null || userIds == null
+                || userIds.length == 0) {
+            return;
+        }
+
+        String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage(
+                pkgSetting);
+        // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival.
+        if (TextUtils.isEmpty(responsibleInstallerPackage)) {
+            Slog.e(TAG, "Can't create archive state: responsible installer is empty");
+            return;
+        }
+        for (int userId : userIds) {
+            var archiveState = PackageArchiver.createArchiveState(archivePackage, userId,
+                    responsibleInstallerPackage);
+            if (archiveState == null) {
+                continue;
+            }
+            pkgSetting
+                    .modifyUserState(userId)
+                    .setArchiveState(archiveState);
+        }
+    }
+
+
     void scheduleWriteSettings() {
         // We normally invalidate when we write settings, but in cases where we delay and
         // coalesce settings writes, this strategy would have us invalidate the cache too late.
@@ -6301,35 +6388,13 @@
         }
 
         @Override
-        public ArchivedPackageParcel getArchivedPackage(String apkPath) {
-            ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-            ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
-                    new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
-            if (result.isError()) {
-                throw new IllegalArgumentException(result.getErrorMessage(), result.getException());
-            }
-            final ApkLite apk = result.getResult();
+        public ArchivedPackageParcel getArchivedPackage(@NonNull String packageName, int userId) {
+            return getArchivedPackageInternal(packageName, userId);
+        }
 
-            ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
-            archPkg.packageName = apk.getPackageName();
-            archPkg.signingDetails = apk.getSigningDetails();
-
-            archPkg.versionCodeMajor = apk.getVersionCodeMajor();
-            archPkg.versionCode = apk.getVersionCode();
-
-            archPkg.targetSdkVersion = apk.getTargetSdkVersion();
-
-            // These get translated in flags important for user data management.
-            archPkg.backupAllowed = String.valueOf(apk.isBackupAllowed());
-            archPkg.defaultToDeviceProtectedStorage = String.valueOf(
-                    apk.isDefaultToDeviceProtectedStorage());
-            archPkg.requestLegacyExternalStorage = String.valueOf(
-                    apk.isRequestLegacyExternalStorage());
-            archPkg.userDataFragile = String.valueOf(apk.isUserDataFragile());
-            archPkg.clearUserDataOnFailedRestoreAllowed = String.valueOf(
-                    apk.isClearUserDataOnFailedRestoreAllowed());
-
-            return archPkg;
+        @Override
+        public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+            return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8d82085..72a7370 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,6 +26,7 @@
 import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
 import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
@@ -82,9 +83,9 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.IBinder;
 import android.os.IUserManager;
+import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.PersistableBundle;
@@ -130,6 +131,7 @@
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
+import libcore.util.HexEncoding;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -248,8 +250,6 @@
                     return runStreamingInstall();
                 case "install-incremental":
                     return runIncrementalInstall();
-                case "install-archived":
-                    return runArchivedInstall();
                 case "install-abandon":
                 case "install-destroy":
                     return runInstallAbandon();
@@ -277,6 +277,10 @@
                     return runUninstall();
                 case "clear":
                     return runClear();
+                case "get-archived-package-metadata":
+                    return runGetArchivedPackageMetadata();
+                case "install-archived":
+                    return runArchivedInstall();
                 case "enable":
                     return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
                 case "disable":
@@ -1808,6 +1812,56 @@
         return doRemoveSplits(sessionId, splitNames, true /*logSuccess*/);
     }
 
+    private int runGetArchivedPackageMetadata() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_CURRENT;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                default:
+                    pw.println("Error: Unknown option: " + opt);
+                    return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+        final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL,
+                "runGetArchivedPackageMetadata");
+
+        try {
+            var archivedPackage = mInterface.getArchivedPackage(packageName, translatedUserId);
+            if (archivedPackage == null) {
+                pw.write("Package not found " + packageName);
+                return -1;
+            }
+
+            Parcel parcel = Parcel.obtain();
+            byte[] bytes;
+            try {
+                parcel.writeParcelable(archivedPackage, 0);
+                bytes = parcel.marshall();
+            } finally {
+                parcel.recycle();
+            }
+
+            String encoded = HexEncoding.encodeToString(bytes);
+            pw.write(encoded);
+        } catch (Exception e) {
+            getErrPrintWriter().println("Failed to get archived package, reason: " + e);
+            pw.println("Failure [failed to get archived package], reason: " + e);
+            return -1;
+        }
+        return 0;
+    }
+
     private int runInstallExisting() throws RemoteException {
         final PrintWriter pw = getOutPrintWriter();
         int userId = UserHandle.USER_CURRENT;
@@ -4146,28 +4200,24 @@
             throw new IllegalArgumentException("Error: Can't open file: " + inPath);
         }
 
-        File tmpFile = null;
+        final String encoded;
         final ParcelFileDescriptor fd = fdWithSize.first;
+        final int size = (int) (long) fdWithSize.second;
         try (InputStream inStream = new AutoCloseInputStream(fd)) {
-            final long identity = Binder.clearCallingIdentity();
-            try {
-                File tmpStagingDir = Environment.getDataAppDirectory(null);
-                tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp");
-
-                try (OutputStream outStream = new FileOutputStream(tmpFile)) {
-                    Streams.copy(inStream, outStream);
-                }
-
-                return mInterface.getArchivedPackage(tmpFile.getAbsolutePath());
-            } finally {
-                if (tmpFile != null) {
-                    tmpFile.delete();
-                }
-                Binder.restoreCallingIdentity(identity);
-            }
+            byte[] bytes = new byte[size];
+            Streams.readFully(inStream, bytes);
+            encoded = new String(bytes);
         } catch (IOException e) {
-            throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e);
+            throw new IllegalArgumentException("Error: Can't load archived package from: " + inPath,
+                    e);
         }
+
+        var result = Metadata.readArchivedPackageParcel(HexEncoding.decode(encoded));
+        if (result == null) {
+            throw new IllegalArgumentException(
+                    "Error: Can't parse archived package from: " + inPath);
+        }
+        return result;
     }
 
     private void processArgForLocalFile(String arg, PackageInstaller.Session session,
@@ -4347,10 +4397,21 @@
             session.commit(receiver.getIntentSender());
             if (!session.isStaged()) {
                 final Intent result = receiver.getResult();
-                final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                        PackageInstaller.STATUS_FAILURE);
+                int status = result.getIntExtra(
+                        PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+                List<String> warnings =
+                        result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS);
                 if (status == PackageInstaller.STATUS_SUCCESS) {
-                    if (logSuccess) {
+                    if (!ArrayUtils.isEmpty(warnings)) {
+                        // Don't start the output string with "Success" because that will make adb
+                        // treat this as a success.
+                        for (String warning : warnings) {
+                            pw.println("Warning: " + warning);
+                        }
+                        // Treat warnings as failure to draw app developers' attention.
+                        status = PackageInstaller.STATUS_FAILURE;
+                        pw.println("Completed with warning(s)");
+                    } else if (logSuccess) {
                         pw.println("Success");
                     }
                 } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index fbe5a51..9e7f043 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -166,16 +166,7 @@
         /** @hide */
         @VisibleForTesting
         public static Metadata forArchived(ArchivedPackageParcel archivedPackage) {
-            Parcel parcel = Parcel.obtain();
-            byte[] bytes;
-            try {
-                parcel.writeParcelable(archivedPackage, 0);
-                bytes = parcel.marshall();
-            } finally {
-                parcel.recycle();
-            }
-
-            return new Metadata(ARCHIVED, bytes, null);
+            return new Metadata(ARCHIVED, writeArchivedPackageParcel(archivedPackage), null);
         }
 
         static Metadata forDataOnlyStreaming(String fileId) {
@@ -270,11 +261,14 @@
             if (getMode() != ARCHIVED) {
                 throw new IllegalStateException("Not an archived package metadata.");
             }
+            return readArchivedPackageParcel(this.mData);
+        }
 
+        static ArchivedPackageParcel readArchivedPackageParcel(byte[] bytes) {
             Parcel parcel = Parcel.obtain();
             ArchivedPackageParcel result;
             try {
-                parcel.unmarshall(this.mData, 0, this.mData.length);
+                parcel.unmarshall(bytes, 0, bytes.length);
                 parcel.setDataPosition(0);
                 result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader());
             } finally {
@@ -282,6 +276,16 @@
             }
             return result;
         }
+
+        static byte[] writeArchivedPackageParcel(ArchivedPackageParcel archivedPackage) {
+            Parcel parcel = Parcel.obtain();
+            try {
+                parcel.writeParcelable(archivedPackage, 0);
+                return parcel.marshall();
+            } finally {
+                parcel.recycle();
+            }
+        }
     }
 
     private static class DataLoader implements DataLoaderService.DataLoader {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 1884caf..2e60064 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -654,6 +654,16 @@
         return (getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    public boolean isRequestLegacyExternalStorage() {
+        return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
+                != 0;
+    }
+
+    public boolean isUserDataFragile() {
+        return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+                != 0;
+    }
+
     public SigningDetails getSigningDetails() {
         return signatures.mSigningDetails;
     }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 4eceb77..cc8e624 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -378,7 +378,6 @@
         ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
                 | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
                 | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
-
         if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
                 && state.isQuarantined()) {
             ai.enabled = false;
@@ -402,6 +401,14 @@
             ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]);
             ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]);
         }
+        ai.isArchived = isArchived(state);
+    }
+
+    // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed.
+    // If an app has isInstalled() == true - it should not be filtered above in any case, currently
+    // it is.
+    private static boolean isArchived(PackageUserState userState) {
+        return userState.getArchiveState() != null;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index c44b8852..bf20653 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1490,12 +1490,9 @@
         if (dir.isDirectory() && dir.canRead()) {
             Collections.addAll(ret, dir.listFiles());
         }
-        // For IoT devices, we check the oem partition for default permissions for each app.
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
-            if (dir.isDirectory() && dir.canRead()) {
-                Collections.addAll(ret, dir.listFiles());
-            }
+        dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
         }
         return ret.isEmpty() ? null : ret.toArray(new File[0]);
     }
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 812e228..f14941b 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
@@ -28,6 +28,7 @@
 import static android.os.Build.VERSION_CODES.DONUT;
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
 import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
 
 import android.annotation.AnyRes;
@@ -509,24 +510,33 @@
                 /* Set the global "on SD card" flag */
                 .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
 
+        var archivedPackage = lite.getArchivedPackage();
+        if (archivedPackage == null) {
+            return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+                    "archivePackage is missing");
+        }
+
         // parseBaseAppBasicFlags
         pkg
                 // Default true
-                .setBackupAllowed(lite.isBackupAllowed())
+                .setBackupAllowed(true)
                 .setClearUserDataAllowed(true)
-                .setClearUserDataOnFailedRestoreAllowed(
-                        lite.isClearUserDataOnFailedRestoreAllowed())
+                .setClearUserDataOnFailedRestoreAllowed(true)
                 .setAllowNativeHeapPointerTagging(true)
                 .setEnabled(true)
                 .setExtractNativeLibrariesRequested(true)
                 // targetSdkVersion gated
                 .setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q)
                 .setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
-                .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage())
+                .setRequestLegacyExternalStorage(
+                        XmlUtils.convertValueToBoolean(archivedPackage.requestLegacyExternalStorage,
+                                targetSdk < Build.VERSION_CODES.Q))
                 .setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P)
                 // Default false
-                .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage())
-                .setUserDataFragile(lite.isUserDataFragile())
+                .setDefaultToDeviceProtectedStorage(XmlUtils.convertValueToBoolean(
+                        archivedPackage.defaultToDeviceProtectedStorage, false))
+                .setUserDataFragile(
+                        XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false))
                 // Ints
                 .setCategory(ApplicationInfo.CATEGORY_UNDEFINED)
                 // Floats Default 0f
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4d38239..b420acd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4773,7 +4773,7 @@
             case KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY:
             case KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL: {
-                if (down && mStylusButtonsEnabled) {
+                if (mStylusButtonsEnabled) {
                     sendSystemKeyToStatusBarAsync(event);
                 }
                 result &= ~ACTION_PASS_TO_USER;
diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING
index 673456f..29d52ff 100644
--- a/services/core/java/com/android/server/security/TEST_MAPPING
+++ b/services/core/java/com/android/server/security/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-    "postsubmit": [
+    "presubmit": [
         {
             "name": "CtsSecurityTestCases",
             "options": [
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index c526016..a5c0fb3 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1454,6 +1454,7 @@
         boolean hasDesiredDemuxCap = request.desiredFilterTypes
                 != DemuxFilterMainType.UNDEFINED;
         int smallestNumOfSupportedCaps = Integer.SIZE + 1;
+        int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
         for (DemuxResource dr : getDemuxResources().values()) {
             if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
                 if (!dr.isInUse()) {
@@ -1476,12 +1477,18 @@
                             currentLowestPriority = priority;
                             isRequestFromSameProcess = (requestClient.getProcessId()
                                 == (getClientProfile(dr.getOwnerClientId())).getProcessId());
+
+                            // reset the smallest caps when lower priority resource is found
+                            smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+
                             shouldUpdate = true;
-                        }
-                        // update smallest caps
-                        if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
-                            smallestNumOfSupportedCaps = numOfSupportedCaps;
-                            shouldUpdate = true;
+                        } else {
+                            // This is the case when the priority is the same as previously found
+                            // one. Update smallest caps when priority.
+                            if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
+                                smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+                                shouldUpdate = true;
+                            }
                         }
                         if (shouldUpdate) {
                             inUseLowestPriorityDrHandle = dr.getHandle();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3c2d0fc..4c525e9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -256,6 +256,15 @@
                     || event != CLOSE_WRITE // includes the MOVED_TO case
                     || wallpaper.imageWallpaperPending;
 
+            if (isMigration) {
+                // When separate lock screen engine is supported, migration will be handled by
+                // WallpaperDestinationChangeHandler.
+                return;
+            }
+            if (!(sysWallpaperChanged || lockWallpaperChanged)) {
+                return;
+            }
+
             if (DEBUG) {
                 Slog.v(TAG, "Wallpaper file change: evt=" + event
                         + " path=" + path
@@ -270,15 +279,6 @@
                         + " needsUpdate=" + needsUpdate);
             }
 
-            if (isMigration) {
-                // When separate lock screen engine is supported, migration will be handled by
-                // WallpaperDestinationChangeHandler.
-                return;
-            }
-            if (!(sysWallpaperChanged || lockWallpaperChanged)) {
-                return;
-            }
-
             int notifyColorsWhich = 0;
             synchronized (mLock) {
                 notifyCallbacksLocked(wallpaper);
@@ -3494,15 +3494,24 @@
         }
     }
 
+    /**
+     * Determines if the given component name is the default component. Note: a null name can be
+     * used to represent the default component.
+     * @param name The component name to check.
+     * @return True if the component name matches the default wallpaper component.
+     */
+    private boolean isDefaultComponent(ComponentName name) {
+        return name == null || name.equals(mDefaultWallpaperComponent);
+    }
+
     private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
         if (wallpaper.connection != null) {
-            if (wallpaper.wallpaperComponent == null) {
-                if (componentName == null) {
-                    if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
-                    // Still using default wallpaper.
-                    return true;
-                }
-            } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+            final ComponentName wallpaperName = wallpaper.wallpaperComponent;
+            if (isDefaultComponent(componentName) && isDefaultComponent(wallpaperName)) {
+                if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+                // Still using default wallpaper.
+                return true;
+            } else if (wallpaperName != null && wallpaperName.equals(componentName)) {
                 // Changing to same wallpaper.
                 if (DEBUG) Slog.v(TAG, "same wallpaper");
                 return true;
@@ -3519,6 +3528,9 @@
         // Has the component changed?
         if (!force && changingToSame(componentName, wallpaper)) {
             try {
+                if (DEBUG_LIVE) {
+                    Slog.v(TAG, "Changing to the same component, ignoring");
+                }
                 if (reply != null) reply.sendResult(null);
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to send callback", e);
@@ -3737,7 +3749,11 @@
             mContext.getMainThreadHandler().removeCallbacks(
                     wallpaper.connection.mTryToRebindRunnable);
 
-            mContext.unbindService(wallpaper.connection);
+            try {
+                mContext.unbindService(wallpaper.connection);
+            } catch (IllegalArgumentException e) {
+                Slog.w(TAG, "Error unbinding wallpaper when detaching", e);
+            }
             wallpaper.connection = null;
             if (wallpaper == mLastWallpaper) {
                 mLastWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dca2b6f..52dafc2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -565,8 +565,8 @@
     boolean forceNewConfig; // force re-create with new config next time
     boolean supportsEnterPipOnTaskSwitch;  // This flag is set by the system to indicate that the
         // activity can enter picture in picture while pausing (only when switching to another task)
+    // The PiP params used when deferring the entering of picture-in-picture.
     PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
-        // The PiP params used when deferring the entering of picture-in-picture.
     boolean shouldDockBigOverlays;
     int launchCount;        // count of launches since last state
     long lastLaunchTime;    // time of last launch of this activity
@@ -1732,6 +1732,16 @@
         mAnimatingActivityRegistry = registry;
     }
 
+    boolean canAutoEnterPip() {
+        // beforeStopping=false since the actual pip-ing will take place after startPausing()
+        final boolean activityCanPip = checkEnterPictureInPictureState(
+                "startActivityUnchecked", false /* beforeStopping */);
+
+        // check if this activity is about to auto-enter pip
+        return activityCanPip && pictureInPictureArgs != null
+                && pictureInPictureArgs.isAutoEnterEnabled();
+    }
+
     /**
      * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
      * {@link #getTask()} is set before this is called.
@@ -7309,6 +7319,12 @@
         }
         return false;
     }
+
+    /**
+     * @return true if a solid color splash screen must be used
+     *         false when an icon splash screen can be used, but the final decision for whether to
+     *               use an icon or solid color splash screen will be made by WmShell.
+     */
     private boolean shouldUseSolidColorSplashScreen(ActivityRecord sourceRecord,
             boolean startActivity, ActivityOptions options, int resolvedTheme) {
         if (sourceRecord == null && !startActivity) {
@@ -7322,39 +7338,36 @@
         }
 
         // setSplashScreenStyle decide in priority of windowSplashScreenBehavior.
-        if (options != null) {
-            final int optionsStyle = options.getSplashScreenStyle();
-            if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) {
-                return true;
-            } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON
+        final int optionsStyle = options != null ? options.getSplashScreenStyle() :
+                SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+        if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR) {
+            return true;
+        } else if (optionsStyle == SplashScreen.SPLASH_SCREEN_STYLE_ICON
                     || isIconStylePreferred(resolvedTheme)) {
-                return false;
-            }
-            // Choose the default behavior for Launcher and SystemUI when the SplashScreen style is
-            // not specified in the ActivityOptions.
-            if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME
-                    || launchedFromUid == Process.SHELL_UID) {
-                return false;
-            } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
-                return true;
-            }
-        } else if (isIconStylePreferred(resolvedTheme)) {
             return false;
         }
-        if (sourceRecord == null) {
-            sourceRecord = searchCandidateLaunchingActivity();
-        }
 
-        if (sourceRecord != null && !sourceRecord.isActivityTypeHome()) {
-            return sourceRecord.mSplashScreenStyleSolidColor;
-        }
+        // Choose the default behavior when neither the ActivityRecord nor the activity theme have
+        // specified a splash screen style.
 
-        // If this activity was launched from Launcher or System for first start, never use a
-        // solid color splash screen.
-        // Need to check sourceRecord before in case this activity is launched from service.
-        return !startActivity || !(mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEM
-                || mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME
-                || launchedFromUid == Process.SHELL_UID);
+        if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_HOME || launchedFromUid == Process.SHELL_UID) {
+            return false;
+        } else if (mLaunchSourceType == LAUNCH_SOURCE_TYPE_SYSTEMUI) {
+            return true;
+        } else {
+            // Need to check sourceRecord in case this activity is launched from a service.
+            if (sourceRecord == null) {
+                sourceRecord = searchCandidateLaunchingActivity();
+            }
+
+            if (sourceRecord != null) {
+                return sourceRecord.mSplashScreenStyleSolidColor;
+            }
+
+            // Use an icon if the activity was launched from System for the first start.
+            // Otherwise, must use solid color splash screen.
+            return mLaunchSourceType != LAUNCH_SOURCE_TYPE_SYSTEM || !startActivity;
+        }
     }
 
     private int getSplashscreenTheme(ActivityOptions options) {
@@ -9180,7 +9193,13 @@
                     getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED
                             ? newParentConfig.windowConfiguration.getWindowingMode()
                             : getRequestedOverrideWindowingMode();
-            if (getWindowingMode() != projectedWindowingMode) {
+            if (getWindowingMode() != projectedWindowingMode
+                    // Do not collect a pip activity about to enter pinned mode
+                    // as a part of WindowOrganizerController#finishTransition().
+                    // If not checked the activity might be collected for the wrong transition,
+                    // such as a TRANSIT_OPEN transition requested right after TRANSIT_PIP.
+                    && !(mWaitForEnteringPinnedMode
+                    && mTransitionController.inFinishingTransition(this))) {
                 mTransitionController.collect(this);
             }
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index a5b1132..25c42b4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -33,9 +33,7 @@
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
@@ -240,11 +238,6 @@
                 getInterceptorInfo(null /* clearOptionsAnimation */);
 
         for (int i = 0; i < callbacks.size(); i++) {
-            final int orderId = callbacks.keyAt(i);
-            if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) {
-                continue;
-            }
-
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
             final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch(
                     interceptorInfo);
@@ -543,11 +536,6 @@
         ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
                 r::clearOptionsAnimationForSiblings);
         for (int i = 0; i < callbacks.size(); i++) {
-            final int orderId = callbacks.keyAt(i);
-            if (!shouldNotifyOnActivityLaunch(orderId, info)) {
-                continue;
-            }
-
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
             callback.onActivityLaunched(taskInfo, r.info, info);
         }
@@ -565,21 +553,4 @@
                 .build();
     }
 
-    private boolean shouldInterceptActivityLaunch(
-            @ActivityInterceptorCallback.OrderedId int orderId,
-            @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
-        if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
-            return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
-        }
-        return true;
-    }
-
-    private boolean shouldNotifyOnActivityLaunch(
-            @ActivityInterceptorCallback.OrderedId int orderId,
-            @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
-        if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
-            return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
-        }
-        return true;
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6999c6a..f38f6b0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -313,6 +313,8 @@
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
     private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
+    private static final String ENABLE_PIP2_IMPLEMENTATION =
+            "persist.wm.debug.enable_pip2_implementation";
     static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
 
@@ -1841,8 +1843,12 @@
             RemoteCallback navigationObserver, BackAnimationAdapter adapter) {
         mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
                 "startBackNavigation()");
-
-        return mBackNavigationController.startBackNavigation(navigationObserver, adapter);
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return mBackNavigationController.startBackNavigation(navigationObserver, adapter);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
     }
 
     /**
@@ -3613,6 +3619,28 @@
         }
     }
 
+    /**
+     * Prepare to enter PiP mode after {@link TransitionController#requestStartTransition}.
+     *
+     * @param r activity auto entering pip
+     * @return true if the activity is about to auto-enter pip or is already in pip mode.
+     */
+    boolean prepareAutoEnterPictureAndPictureMode(ActivityRecord r) {
+        // If the activity is already in picture in picture mode, then just return early
+        if (r.inPinnedWindowingMode()) {
+            return true;
+        }
+
+        if (r.canAutoEnterPip() && getTransitionController().getCollectingTransition() != null) {
+            // This will be used later to construct TransitionRequestInfo for Shell to resolve.
+            // It will also be passed into a direct moveActivityToPinnedRootTask() call via
+            // startTransition()
+            getTransitionController().getCollectingTransition().setPipActivity(r);
+            return true;
+        }
+        return false;
+    }
+
     boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
             @NonNull PictureInPictureParams params, boolean fromClient) {
         return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */);
@@ -7183,4 +7211,8 @@
             ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
         }
     }
+
+    static boolean isPip2ExperimentEnabled() {
+        return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e523119..9bfc553 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -795,17 +795,13 @@
             return false;
         }
 
-        // Try pausing the existing resumed activity in the same TaskFragment if any.
-        final TaskFragment taskFragment = r.getTaskFragment();
-        if (taskFragment != null && taskFragment.getResumedActivity() != null) {
-            if (taskFragment.startPausing(mUserLeaving, false /* uiSleeping */, r, "realStart")) {
-                return false;
-            }
+        // Try pausing the existing resumed activity in the Task if any.
+        final Task task = r.getTask();
+        if (task.pauseActivityIfNeeded(r, "realStart")) {
+            return false;
         }
 
-        final Task task = r.getTask();
         final Task rootTask = task.getRootTask();
-
         beginDeferResume();
         // The LaunchActivityItem also contains process configuration, so the configuration change
         // from WindowProcessController#setProcess can be deferred. The major reason is that if
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 7cd07d6..b499dad 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -83,6 +83,11 @@
     @Nullable private Rect mLastRecordedBounds = null;
 
     /**
+     * The last size of the surface mirrored out to.
+     */
+    @Nullable private Point mLastConsumingSurfaceSize = new Point(0, 0);
+
+    /**
      * The last configuration orientation.
      */
     @Configuration.Orientation
@@ -141,60 +146,64 @@
      */
     void onConfigurationChanged(@Configuration.Orientation int lastOrientation) {
         // Update surface for MediaProjection, if this DisplayContent is being used for recording.
-        if (isCurrentlyRecording() && mLastRecordedBounds != null) {
-            // Recording has already begun, but update recording since the display is now on.
-            if (mRecordedWindowContainer == null) {
+        if (!isCurrentlyRecording() || mLastRecordedBounds == null) {
+            return;
+        }
+
+        // Recording has already begun, but update recording since the display is now on.
+        if (mRecordedWindowContainer == null) {
+            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                    "Content Recording: Unexpectedly null window container; unable to update "
+                            + "recording for display %d",
+                    mDisplayContent.getDisplayId());
+            return;
+        }
+
+        // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
+        //  inaccurate.
+        if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+            final Task capturedTask = mRecordedWindowContainer.asTask();
+            if (capturedTask.inPinnedWindowingMode()) {
                 ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                        "Content Recording: Unexpectedly null window container; unable to update "
-                                + "recording for display %d",
+                        "Content Recording: Display %d was already recording, but "
+                                + "pause capture since the task is in PIP",
                         mDisplayContent.getDisplayId());
+                pauseRecording();
                 return;
             }
+        }
 
-            // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
-            //  inaccurate.
-            if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
-                final Task capturedTask = mRecordedWindowContainer.asTask();
-                if (capturedTask.inPinnedWindowingMode()) {
-                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                            "Content Recording: Display %d was already recording, but "
-                                    + "pause capture since the task is in PIP",
-                            mDisplayContent.getDisplayId());
-                    pauseRecording();
-                    return;
-                }
-            }
-
-            ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                    "Content Recording: Display %d was already recording, so apply "
-                            + "transformations if necessary",
-                    mDisplayContent.getDisplayId());
-            // Retrieve the size of the region to record, and continue with the update
-            // if the bounds or orientation has changed.
-            final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
-            @Configuration.Orientation int recordedContentOrientation =
-                    mRecordedWindowContainer.getConfiguration().orientation;
-            if (!mLastRecordedBounds.equals(recordedContentBounds)
-                    || lastOrientation != recordedContentOrientation) {
-                Point surfaceSize = fetchSurfaceSizeIfPresent();
-                if (surfaceSize != null) {
-                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                            "Content Recording: Going ahead with updating recording for display "
-                                    + "%d to new bounds %s and/or orientation %d.",
-                            mDisplayContent.getDisplayId(), recordedContentBounds,
-                            recordedContentOrientation);
-                    updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
-                            recordedContentBounds, surfaceSize);
-                } else {
-                    // If the surface removed, do nothing. We will handle this via onDisplayChanged
-                    // (the display will be off if the surface is removed).
-                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                            "Content Recording: Unable to update recording for display %d to new "
-                                    + "bounds %s and/or orientation %d, since the surface is not "
-                                    + "available.",
-                            mDisplayContent.getDisplayId(), recordedContentBounds,
-                            recordedContentOrientation);
-                }
+        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                "Content Recording: Display %d was already recording, so apply "
+                        + "transformations if necessary",
+                mDisplayContent.getDisplayId());
+        // Retrieve the size of the region to record, and continue with the update
+        // if the bounds or orientation has changed.
+        final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
+        @Configuration.Orientation int recordedContentOrientation =
+                mRecordedWindowContainer.getConfiguration().orientation;
+        final Point surfaceSize = fetchSurfaceSizeIfPresent();
+        if (!mLastRecordedBounds.equals(recordedContentBounds)
+                || lastOrientation != recordedContentOrientation
+                || !mLastConsumingSurfaceSize.equals(surfaceSize)) {
+            if (surfaceSize != null) {
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Going ahead with updating recording for display "
+                                + "%d to new bounds %s and/or orientation %d and/or surface "
+                                + "size %s",
+                        mDisplayContent.getDisplayId(), recordedContentBounds,
+                        recordedContentOrientation, surfaceSize);
+                updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
+                        recordedContentBounds, surfaceSize);
+            } else {
+                // If the surface removed, do nothing. We will handle this via onDisplayChanged
+                // (the display will be off if the surface is removed).
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Unable to update recording for display %d to new "
+                                + "bounds %s and/or orientation %d and/or surface size %s, "
+                                + "since the surface is not available.",
+                        mDisplayContent.getDisplayId(), recordedContentBounds,
+                        recordedContentOrientation, surfaceSize);
             }
         }
     }
@@ -498,10 +507,13 @@
         }
 
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x "
-                        + "%d for display %d",
+                "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
+                        + "recorded content size) %d x %d for display %d; display has size %d x "
+                        + "%d; surface has size %d x %d",
                 shiftedX, shiftedY, scale, recordedContentBounds.width(),
-                recordedContentBounds.height(), mDisplayContent.getDisplayId());
+                recordedContentBounds.height(), mDisplayContent.getDisplayId(),
+                mDisplayContent.getConfiguration().screenWidthDp,
+                mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
 
         transaction
                 // Crop the area to capture to exclude the 'extra' wallpaper that is used
@@ -515,6 +527,8 @@
                 // the content will no longer be centered in the output surface.
                 .setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
         mLastRecordedBounds = new Rect(recordedContentBounds);
+        mLastConsumingSurfaceSize.x = surfaceSize.x;
+        mLastConsumingSurfaceSize.y = surfaceSize.y;
         // Request to notify the client about the resize.
         mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
                 mLastRecordedBounds.width(), mLastRecordedBounds.height());
@@ -523,6 +537,7 @@
     /**
      * Returns a non-null {@link Point} if the surface is present, or null otherwise
      */
+    @Nullable
     private Point fetchSurfaceSizeIfPresent() {
         // Retrieve the default size of the surface the app provided to
         // MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 996d666..4309e72 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6040,8 +6040,9 @@
                 mOffTokenAcquirer.release(mDisplayId);
             }
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
-                    "Content Recording: Display %d state is now (%d), so update recording?",
-                    mDisplayId, displayState);
+                    "Content Recording: Display %d state was (%d), is now (%d), so update "
+                            + "recording?",
+                    mDisplayId, lastDisplayState, displayState);
             if (lastDisplayState != displayState) {
                 // If state is on due to surface being added, then start recording.
                 // If state is off due to surface being removed, then stop recording.
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 8519e4d..0f1a105 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -21,6 +21,7 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
@@ -32,6 +33,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.MY_PID;
 import static com.android.server.wm.WindowManagerService.MY_UID;
+
 import static java.util.concurrent.CompletableFuture.completedFuture;
 
 import android.animation.Animator;
@@ -46,6 +48,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.InputConfig;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -183,13 +186,8 @@
         // Crop the input surface to the display size.
         mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
 
-        // Make trusted overlay to not block any touches while D&D ongoing and allowing
-        // touches to pass through to windows underneath. This allows user to interact with the
-        // UI to navigate while dragging.
-        h.setTrustedOverlay(mTransaction, mInputSurface, true);
         mTransaction.show(mInputSurface)
                 .setInputWindowInfo(mInputSurface, h)
-                .setTrustedOverlay(mInputSurface, true)
                 .setLayer(mInputSurface, Integer.MAX_VALUE)
                 .setCrop(mInputSurface, mTmpClipRect);
 
@@ -379,6 +377,11 @@
             mDragWindowHandle.ownerUid = MY_UID;
             mDragWindowHandle.scaleFactor = 1.0f;
 
+            // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
+            // touches to pass through to windows underneath. This allows user to interact with the
+            // UI to navigate while dragging.
+            mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
+
             // The drag window cannot receive new touches.
             mDragWindowHandle.touchableRegion.setEmpty();
 
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930d..39622c1 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -74,7 +74,7 @@
         mWindowHandle.ownerPid = WindowManagerService.MY_PID;
         mWindowHandle.ownerUid = WindowManagerService.MY_UID;
         mWindowHandle.scaleFactor = 1.0f;
-        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
+        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.TRUSTED_OVERLAY;
 
         mInputSurface = mService.makeSurfaceBuilder(
                         mService.mRoot.getDisplayContent(displayId).getSession())
@@ -129,14 +129,12 @@
 
     void show(SurfaceControl.Transaction t, WindowContainer w) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
     }
 
     void show(SurfaceControl.Transaction t, int layer) {
         t.show(mInputSurface);
-        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, layer);
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f77da62..825d38b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -40,10 +40,12 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.Nullable;
@@ -730,7 +732,7 @@
                 new InputWindowHandle(null /* inputApplicationHandle */, displayId));
         inputWindowHandle.setName(name);
         inputWindowHandle.setLayoutParamsType(TYPE_SECURE_SYSTEM_OVERLAY);
-        inputWindowHandle.setTrustedOverlay(t, sc, true);
+        inputWindowHandle.setTrustedOverlay(true);
         populateOverlayInputInfo(inputWindowHandle);
         setInputWindowInfoIfNeeded(t, sc, inputWindowHandle);
     }
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 90d81bd..64b7a60 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -195,11 +195,6 @@
         mChanged = true;
     }
 
-    void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
-            boolean trustedOverlay) {
-        mHandle.setTrustedOverlay(t, sc, trustedOverlay);
-    }
-
     void setOwnerPid(int pid) {
         if (mHandle.ownerPid == pid) {
             return;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 579fd14..45cf10b 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -29,7 +29,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.FeatureFlags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -91,6 +90,13 @@
             "enable_app_compat_user_aspect_ratio_fullscreen";
     private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
 
+    // Whether the letterbox wallpaper style is enabled by default
+    private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER =
+            "enable_letterbox_background_wallpaper";
+
+    // TODO(b/290048978): Enable wallpaper as default letterbox background.
+    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false;
+
     /**
      * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
      * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -179,9 +185,6 @@
     @NonNull
     private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
 
-    @NonNull
-    private final FeatureFlags mFeatureFlags;
-
     // Aspect ratio of letterbox for fixed orientation, values <=
     // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
     private float mFixedOrientationLetterboxAspectRatio;
@@ -298,8 +301,7 @@
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
     @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
-    LetterboxConfiguration(@NonNull final Context systemUiContext,
-                           @NonNull FeatureFlags featureFags) {
+    LetterboxConfiguration(@NonNull final Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
                         systemUiContext, /* forBookMode */ false),
@@ -308,16 +310,14 @@
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
                         systemUiContext, /* forBookMode */ true),
                 () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                        systemUiContext, /* forTabletopMode */ true)),
-                featureFags);
+                        systemUiContext, /* forTabletopMode */ true)));
     }
 
     @VisibleForTesting
     LetterboxConfiguration(@NonNull final Context systemUiContext,
-            @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister,
-            @NonNull FeatureFlags featureFags) {
+            @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
         mContext = systemUiContext;
-        mFeatureFlags = featureFags;
+
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
                 R.dimen.config_fixedOrientationLetterboxAspectRatio);
         mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
@@ -385,6 +385,8 @@
                         DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS,
                         mContext.getResources().getBoolean(
                                 R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
+                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER,
+                        DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true)
                 .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
                         DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
                         mContext.getResources().getBoolean(
@@ -542,7 +544,8 @@
     }
 
     /**
-     * Resets letterbox background type value depending on the built time and runtime flags.
+     * Resets letterbox background type value depending on the
+     * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags.
      *
      * <p>If enabled, the letterbox background type value is set toZ
      * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value
@@ -552,11 +555,12 @@
         mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
     }
 
-    // Returns LETTERBOX_BACKGROUND_WALLPAPER if the flag is enabled or the value in
-    // com.android.internal.R.integer.config_letterboxBackgroundType if the flag is disabled.
+    // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled
+    // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag
+    // is disabled.
     @LetterboxBackgroundType
     private int getDefaultLetterboxBackgroundType() {
-        return mFeatureFlags.letterboxBackgroundWallpaperFlag()
+        return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER)
                 ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType;
     }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 01786be..3639e1b 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1126,11 +1126,25 @@
         return computeAspectRatio(bounds);
     }
 
+    /**
+     * Whether we should enable users to resize the current app.
+     */
+    boolean shouldEnableUserAspectRatioSettings() {
+        // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
+        // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
+        // the current app doesn't opt-out so the first part of the predicate is true.
+        return !FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+                && mActivityRecord.mDisplayContent != null
+                && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
+    }
+
+    /**
+     * Whether we should apply the user aspect ratio override to the min aspect ratio for the
+     * current app.
+     */
     boolean shouldApplyUserMinAspectRatioOverride() {
-        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
-                || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
-                || mActivityRecord.mDisplayContent == null
-                || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+        if (!shouldEnableUserAspectRatioSettings()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index bbb8563..2c142cb 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -40,11 +40,9 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
-import android.os.IBinder;
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
@@ -57,7 +55,6 @@
 import com.android.internal.R;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.server.display.DisplayControl;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
@@ -171,32 +168,10 @@
         try {
             final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
             if (isSizeChanged) {
-                final DisplayAddress address = displayInfo.address;
-                if (!(address instanceof DisplayAddress.Physical)) {
-                    Slog.e(TAG, "Display does not have a physical address: " + displayId);
-                    return;
-                }
-                final DisplayAddress.Physical physicalAddress =
-                        (DisplayAddress.Physical) address;
-                final IBinder displayToken = DisplayControl.getPhysicalDisplayToken(
-                        physicalAddress.getPhysicalDisplayId());
-                if (displayToken == null) {
-                    Slog.e(TAG, "Display token is null.");
-                    return;
-                }
                 // Temporarily not skip screenshot for the rounded corner overlays and screenshot
                 // the whole display to include the rounded corner overlays.
                 setSkipScreenshotForRoundedCornerOverlays(false, t);
-                mRoundedCornerOverlay = displayContent.findRoundedCornerOverlays();
-                final ScreenCapture.DisplayCaptureArgs captureArgs =
-                        new ScreenCapture.DisplayCaptureArgs.Builder(displayToken)
-                                .setSourceCrop(new Rect(0, 0, width, height))
-                                .setAllowProtected(true)
-                                .setCaptureSecureLayers(true)
-                                .setHintForSeamlessTransition(true)
-                                .build();
-                screenshotBuffer = ScreenCapture.captureDisplay(captureArgs);
-            } else {
+            }
                 ScreenCapture.LayerCaptureArgs captureArgs =
                         new ScreenCapture.LayerCaptureArgs.Builder(
                                 displayContent.getSurfaceControl())
@@ -206,7 +181,6 @@
                                 .setHintForSeamlessTransition(true)
                                 .build();
                 screenshotBuffer = ScreenCapture.captureLayers(captureArgs);
-            }
 
             if (screenshotBuffer == null) {
                 Slog.w(TAG, "Unable to take screenshot of display " + displayId);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 21526e7..b4b8a74 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1262,6 +1262,37 @@
         return null;
     }
 
+    boolean pauseActivityIfNeeded(@Nullable ActivityRecord resuming, @NonNull String reason) {
+        if (!isLeafTask()) {
+            return false;
+        }
+
+        final int[] someActivityPaused = {0};
+        // Check if the direct child resumed activity in the leaf task needed to be paused if
+        // the leaf task is not a leaf task fragment.
+        if (!isLeafTaskFragment()) {
+            final ActivityRecord top = topRunningActivity();
+            final ActivityRecord resumedActivity = getResumedActivity();
+            if (resumedActivity != null && top.getTaskFragment() != this) {
+                // Pausing the resumed activity because it is occluded by other task fragment.
+                if (startPausing(false /* uiSleeping*/, resuming, reason)) {
+                    someActivityPaused[0]++;
+                }
+            }
+        }
+
+        forAllLeafTaskFragments((taskFrag) -> {
+            final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
+            if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
+                if (taskFrag.startPausing(false /* uiSleeping*/, resuming, reason)) {
+                    someActivityPaused[0]++;
+                }
+            }
+        }, true /* traverseTopToBottom */);
+
+        return someActivityPaused[0] > 0;
+    }
+
     void updateTaskMovement(boolean toTop, boolean toBottom, int position) {
         EventLogTags.writeWmTaskMoved(mTaskId, getRootTaskId(), getDisplayId(), toTop ? 1 : 0,
                 position);
@@ -3478,9 +3509,9 @@
             }
         }
         // User Aspect Ratio Settings is enabled if the app is not in SCM
-        info.topActivityEligibleForUserAspectRatioButton =
-                mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
-                        && top != null && !info.topActivityInSizeCompat;
+        info.topActivityEligibleForUserAspectRatioButton = top != null
+                && !info.topActivityInSizeCompat
+                && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings();
         info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
     }
 
@@ -5102,7 +5133,6 @@
 
     void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
             boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
-        final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask);
         Task rTask = r.getTask();
         final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
         final boolean isOrhasTask = rTask == this || hasChild(rTask);
@@ -5166,8 +5196,10 @@
             // supporting picture-in-picture while pausing only if the starting activity
             // would not be considered an overlay on top of the current activity
             // (eg. not fullscreen, or the assistant)
-            enableEnterPipOnTaskSwitch(pipCandidate,
-                    null /* toFrontTask */, r, options);
+            if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+                final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask);
+                enableEnterPipOnTaskSwitch(pipCandidate, null /* toFrontTask */, r, options);
+            }
         }
         boolean doShow = true;
         if (newTask) {
@@ -5240,7 +5272,7 @@
      * enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or
      * {@param toFrontActivity} should be set.
      */
-    private static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate,
+    static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate,
             @Nullable Task toFrontTask, @Nullable ActivityRecord toFrontActivity,
             @Nullable ActivityOptions opts) {
         if (pipCandidate == null) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 9af12ad..ae794a8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1271,27 +1271,9 @@
     boolean pauseBackTasks(ActivityRecord resuming) {
         final int[] someActivityPaused = {0};
         forAllLeafTasks(leafTask -> {
-            // Check if the direct child resumed activity in the leaf task needed to be paused if
-            // the leaf task is not a leaf task fragment.
-            if (!leafTask.isLeafTaskFragment()) {
-                final ActivityRecord top = topRunningActivity();
-                final ActivityRecord resumedActivity = leafTask.getResumedActivity();
-                if (resumedActivity != null && top.getTaskFragment() != leafTask) {
-                    // Pausing the resumed activity because it is occluded by other task fragment.
-                    if (leafTask.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
-                        someActivityPaused[0]++;
-                    }
-                }
+            if (leafTask.pauseActivityIfNeeded(resuming, "pauseBackTasks")) {
+                someActivityPaused[0]++;
             }
-
-            leafTask.forAllLeafTaskFragments((taskFrag) -> {
-                final ActivityRecord resumedActivity = taskFrag.getResumedActivity();
-                if (resumedActivity != null && !taskFrag.canBeResumed(resuming)) {
-                    if (taskFrag.startPausing(false /* uiSleeping*/, resuming, "pauseBackTasks")) {
-                        someActivityPaused[0]++;
-                    }
-                }
-            }, true /* traverseTopToBottom */);
         }, true /* traverseTopToBottom */);
         return someActivityPaused[0] > 0;
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 57f44cb..d1b5350 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1643,7 +1643,17 @@
             // next activity.
             final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
                     "shouldAutoPipWhilePausing", userLeaving);
-            if (userLeaving && resumingOccludesParent && lastResumedCanPip
+
+            if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+                // If a new task is being launched, then mark the existing top activity as
+                // supporting picture-in-picture while pausing only if the starting activity
+                // would not be considered an overlay on top of the current activity
+                // (eg. not fullscreen, or the assistant)
+                Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
+                        resuming, resuming.getOptions());
+            }
+            if (prev.supportsEnterPipOnTaskSwitch && userLeaving
+                    && resumingOccludesParent && lastResumedCanPip
                     && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
                 shouldAutoPip = true;
             } else if (!lastResumedCanPip) {
@@ -1656,7 +1666,12 @@
         }
 
         if (prev.attachedToProcess()) {
-            if (shouldAutoPip) {
+            if (shouldAutoPip && ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+                prev.mPauseSchedulePendingForPip = true;
+                boolean willAutoPip = mAtmService.prepareAutoEnterPictureAndPictureMode(prev);
+                ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, requesting PIP mode "
+                        + "via requestStartTransition(): %s, willAutoPip: %b", prev, willAutoPip);
+            } else if (shouldAutoPip) {
                 prev.mPauseSchedulePendingForPip = true;
                 boolean didAutoPip = mAtmService.enterPictureInPictureMode(
                         prev, prev.pictureInPictureArgs, false /* fromClient */);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 843e6d1..bbafa25 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -174,6 +174,8 @@
     private final Token mToken;
     private IApplicationThread mRemoteAnimApp;
 
+    private @Nullable ActivityRecord mPipActivity;
+
     /** Only use for clean-up after binder death! */
     private SurfaceControl.Transaction mStartTransaction = null;
     private SurfaceControl.Transaction mFinishTransaction = null;
@@ -510,6 +512,21 @@
     }
 
     /**
+     * Set the pip-able activity participating in this transition.
+     * @param pipActivity activity about to enter pip
+     */
+    void setPipActivity(@Nullable ActivityRecord pipActivity) {
+        mPipActivity = pipActivity;
+    }
+
+    /**
+     * @return pip-able activity participating in this transition.
+     */
+    @Nullable ActivityRecord getPipActivity() {
+        return mPipActivity;
+    }
+
+    /**
      * Only set flag to the parent tasks and activity itself.
      */
     private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
@@ -1597,13 +1614,6 @@
             }
         }
 
-        // Take task snapshots before the animation so that we can capture IME before it gets
-        // transferred. If transition is transient, IME won't be moved during the transition and
-        // the tasks are still live, so we take the snapshot at the end of the transition instead.
-        if (mTransientLaunches == null) {
-            mController.mSnapshotController.onTransactionReady(mType, mTargets);
-        }
-
         // This is non-null only if display has changes. It handles the visible windows that don't
         // need to be participated in the transition.
         for (int i = 0; i < mTargetDisplays.size(); ++i) {
@@ -1654,6 +1664,16 @@
 
         reportStartReasonsToLogger();
 
+        // Take snapshots for closing tasks/activities before the animation finished but after
+        // dispatching onTransitionReady, so IME (if there is) can be captured together and the
+        // time spent on snapshot won't delay the start of animation. Note that if this transition
+        // is transient (mTransientLaunches != null), the snapshot will be captured at the end of
+        // the transition, because IME won't move be moved during the transition and the tasks are
+        // still live.
+        if (mTransientLaunches == null) {
+            mController.mSnapshotController.onTransactionReady(mType, mTargets);
+        }
+
         // Since we created root-leash but no longer reference it from core, release it now
         info.releaseAnimSurfaces();
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 0d78701..7d2933a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -705,13 +705,21 @@
         try {
             ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Requesting StartTransition: %s", transition);
-            ActivityManager.RunningTaskInfo info = null;
+            ActivityManager.RunningTaskInfo startTaskInfo = null;
+            ActivityManager.RunningTaskInfo pipTaskInfo = null;
             if (startTask != null) {
-                info = new ActivityManager.RunningTaskInfo();
-                startTask.fillTaskInfo(info);
+                startTaskInfo = startTask.getTaskInfo();
             }
-            final TransitionRequestInfo request = new TransitionRequestInfo(
-                    transition.mType, info, remoteTransition, displayChange, transition.getFlags());
+
+            // set the pip task in the request if provided
+            if (mCollectingTransition.getPipActivity() != null) {
+                pipTaskInfo = mCollectingTransition.getPipActivity().getTask().getTaskInfo();
+            }
+
+            final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
+                    startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
+                    transition.getFlags());
+
             transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
             transition.mLogger.mRequest = request;
             mTransitionPlayer.requestStartTransition(transition.getToken(), request);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 805e7ff..a4d43d8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -45,6 +45,7 @@
 import android.view.WindowInfo;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.inputmethod.ImeTracker;
+import android.window.ScreenCapture;
 
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
@@ -954,4 +955,19 @@
 
     /** Returns the SurfaceControl accessibility services should use for accessibility overlays. */
     public abstract SurfaceControl getA11yOverlayLayer(int displayId);
+
+    /**
+     * 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.
+     */
+    public abstract void captureDisplay(int displayId,
+                                        @Nullable ScreenCapture.CaptureArgs captureArgs,
+                                        ScreenCapture.ScreenCaptureListener listener);
+
+    /**
+     * Device has a software navigation bar (separate from the status bar) on specific display.
+     *
+     * @param displayId the id of display to check if there is a software navigation bar.
+     */
+    public abstract boolean hasNavigationBar(int displayId);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e720446..8fe104c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,6 +97,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
 import static android.window.WindowProviderService.isWindowProviderService;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -333,7 +334,6 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
-import com.android.window.flags.FeatureFlagsImpl;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -1175,8 +1175,7 @@
 
         mLetterboxConfiguration = new LetterboxConfiguration(
                 // Using SysUI context to have access to Material colors extracted from Wallpaper.
-                ActivityThread.currentActivityThread().getSystemUiContext(),
-                new FeatureFlagsImpl());
+                ActivityThread.currentActivityThread().getSystemUiContext());
 
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -2494,6 +2493,14 @@
                 outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
             }
 
+            // TODO (b/298562855): Remove this after identifying the reason why the frame is empty.
+            if (win.mAttrs.providedInsets != null && win.getFrame().isEmpty()) {
+                Slog.w(TAG, "Empty frame of " + win
+                        + " configChanged=" + configChanged
+                        + " frame=" + win.getFrame().toShortString()
+                        + " attrs=" + attrs);
+            }
+
             ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
                     win, focusMayChange);
 
@@ -8408,6 +8415,17 @@
         }
 
         @Override
+        public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
+                                   ScreenCapture.ScreenCaptureListener listener) {
+            WindowManagerService.this.captureDisplay(displayId, captureArgs, listener);
+        }
+
+        @Override
+        public boolean hasNavigationBar(int displayId) {
+            return WindowManagerService.this.hasNavigationBar(displayId);
+        }
+
+        @Override
         public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) {
             synchronized (mGlobalLock) {
                 mImeTargetChangeListener = listener;
@@ -8869,6 +8887,11 @@
             h.inputConfig |= InputConfig.NOT_FOCUSABLE;
         }
 
+        //  Check private trusted overlay flag to set trustedOverlay field of input window handle.
+        if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
+            h.inputConfig |= InputConfig.TRUSTED_OVERLAY;
+        }
+
         h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         h.ownerUid = callingUid;
         h.ownerPid = callingPid;
@@ -8888,8 +8911,6 @@
         }
 
         final SurfaceControl.Transaction t = mTransactionFactory.get();
-        //  Check private trusted overlay flag to set trustedOverlay field of input window handle.
-        h.setTrustedOverlay(t, surface, (privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0);
         t.setInputWindowInfo(surface, h);
         t.apply();
         t.close();
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bfe0553..74a0bafd3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1279,6 +1279,7 @@
             mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
             mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
             mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+            mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
             mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
             mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
             mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 822082b..b12cc0b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,7 +28,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -99,6 +98,7 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -1112,9 +1112,7 @@
         mInputWindowHandle.setName(getName());
         mInputWindowHandle.setPackageName(mAttrs.packageName);
         mInputWindowHandle.setLayoutParamsType(mAttrs.type);
-        if (!USE_SURFACE_TRUSTED_OVERLAY) {
-            mInputWindowHandle.setTrustedOverlay(isWindowTrustedOverlay());
-        }
+        mInputWindowHandle.setTrustedOverlay(shouldWindowHandleBeTrusted(s));
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -1195,12 +1193,12 @@
                 : service.mAtmService.getProcessController(s.mPid, s.mUid);
     }
 
-    private boolean isWindowTrustedOverlay() {
+    boolean shouldWindowHandleBeTrusted(Session s) {
         return InputMonitor.isTrustedOverlay(mAttrs.type)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
-                        && mSession.mCanAddInternalSystemWindow)
+                        && s.mCanAddInternalSystemWindow)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY) != 0
-                        && mSession.mCanCreateSystemApplicationOverlay);
+                        && s.mCanCreateSystemApplicationOverlay);
     }
 
     int getTouchOcclusionMode() {
@@ -5194,9 +5192,6 @@
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
-            if (USE_SURFACE_TRUSTED_OVERLAY) {
-                getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
-            }
         }
         super.prepareSurfaces();
     }
@@ -5949,13 +5944,7 @@
     }
 
     boolean isTrustedOverlay() {
-        if (USE_SURFACE_TRUSTED_OVERLAY) {
-            WindowState parentWindow = getParentWindow();
-            return isWindowTrustedOverlay() || (parentWindow != null
-                    && parentWindow.isWindowTrustedOverlay());
-        } else {
-            return mInputWindowHandle.isTrustedOverlay();
-        }
+        return mInputWindowHandle.isTrustedOverlay();
     }
 
     public boolean receiveFocusFromTapOutside() {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index d22e02e..4203e89 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -50,6 +50,13 @@
                             maxOccurs="1"/>
                 <xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
                             maxOccurs="1"/>
+
+                <xs:element name="hdrBrightnessConfig" type="hdrBrightnessConfig"
+                            minOccurs="0" maxOccurs="1">
+                    <xs:annotation name="nullable"/>
+                    <xs:annotation name="final"/>
+                </xs:element>
+
                 <xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
                 <xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
                             maxOccurs="1"/>
@@ -238,6 +245,31 @@
         </xs:all>
     </xs:complexType>
 
+    <!-- brightness config for HDR content -->
+    <xs:complexType name="hdrBrightnessConfig">
+        <!-- lux level from light sensor to screen brightness recommended max value map. -->
+        <xs:element name="brightnessMap" type="nonNegativeFloatToFloatMap">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- Debounce for brightness increase in millis -->
+        <xs:element name="brightnessIncreaseDebounceMillis" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- Debounce for brightness decrease in millis -->
+        <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- Animation time for brightness increase in millis -->
+        <xs:element  name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- Animation time for brightness decrease in millis -->
+        <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+            <xs:annotation name="final"/>
+        </xs:element>
+    </xs:complexType>
+
     <!-- Maps to PowerManager.THERMAL_STATUS_* values. -->
     <xs:simpleType name="thermalStatus">
         <xs:restriction base="xs:string">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6364c1f..ebd9b1c 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -101,6 +101,7 @@
     method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
     method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
     method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+    method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
     method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
     method public final com.android.server.display.config.SensorDetails getLightSensor();
     method public com.android.server.display.config.LuxThrottling getLuxThrottling();
@@ -130,6 +131,7 @@
     method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
     method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
     method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+    method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
     method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
     method public final void setLightSensor(com.android.server.display.config.SensorDetails);
     method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
@@ -168,6 +170,20 @@
     method public final void setTimeWindowSecs_all(@NonNull java.math.BigInteger);
   }
 
+  public class HdrBrightnessConfig {
+    ctor public HdrBrightnessConfig();
+    method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
+    method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
+    method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
+    method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
+    method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+    method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
+    method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
+    method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
+    method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
+    method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+  }
+
   public class HighBrightnessMode {
     ctor public HighBrightnessMode();
     method @NonNull public final boolean getAllowInLowPowerMode_all();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d8c684f..82d39d6 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -35,11 +35,13 @@
 import android.credentials.CreateCredentialRequest;
 import android.credentials.CredentialOption;
 import android.credentials.CredentialProviderInfo;
+import android.credentials.GetCandidateCredentialsRequest;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialRequest;
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
+import android.credentials.IGetCandidateCredentialsCallback;
 import android.credentials.IGetCredentialCallback;
 import android.credentials.IPrepareGetCredentialCallback;
 import android.credentials.ISetEnabledProvidersCallback;
@@ -96,6 +98,9 @@
     private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
             "enable_credential_manager";
 
+    private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+            "enable_credential_description_api";
+
     private final Context mContext;
 
     /** Cache of system service list per user id. */
@@ -321,7 +326,14 @@
     }
 
     public static boolean isCredentialDescriptionApiEnabled() {
-        return true;
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+                    false);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
     }
 
     @SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -461,6 +473,17 @@
 
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
         @Override
+        public ICancellationSignal getCandidateCredentials(
+                GetCandidateCredentialsRequest request,
+                IGetCandidateCredentialsCallback callback,
+                final String callingPackage) {
+            Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
+                    + callingPackage);
+            // TODO(): Implement
+            return CancellationSignal.createTransport();
+        }
+
+        @Override
         public ICancellationSignal executeGetCredential(
                 GetCredentialRequest request,
                 IGetCredentialCallback callback,
@@ -954,7 +977,7 @@
             Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
 
             if (!isCredentialDescriptionApiEnabled()) {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Feature not supported");
             }
 
             enforceCallingPackage(callingPackage, Binder.getCallingUid());
@@ -974,7 +997,7 @@
 
 
             if (!isCredentialDescriptionApiEnabled()) {
-                throw new UnsupportedOperationException();
+                throw new UnsupportedOperationException("Feature not supported");
             }
 
             enforceCallingPackage(callingPackage, Binder.getCallingUid());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 21b1291..d0ead14 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1472,11 +1472,10 @@
         synchronized (mLock) {
             clear();
             new DevicePoliciesReaderWriter().readFromFileLocked();
-            reapplyAllPoliciesLocked();
         }
     }
 
-    private <V> void reapplyAllPoliciesLocked() {
+    <V> void reapplyAllPoliciesLocked() {
         for (PolicyKey policy : mGlobalPolicies.keySet()) {
             PolicyState<?> policyState = mGlobalPolicies.get(policy);
             // Policy definition and value will always be of the same type
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 50dc061..84d1a45 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3347,6 +3347,11 @@
                 mOwners.systemReady();
                 applyManagedSubscriptionsPolicyIfRequired();
                 break;
+            case SystemService.PHASE_SYSTEM_SERVICES_READY:
+                synchronized (getLockObject()) {
+                    mDevicePolicyEngine.reapplyAllPoliciesLocked();
+                }
+                break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
                 synchronized (getLockObject()) {
                     migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
@@ -15836,6 +15841,83 @@
     }
 
     /**
+     * @param restriction The restriction enforced by admin. It could be any user restriction or
+     *                    policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA},
+     *                    {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE} and  {@link
+     *                    DevicePolicyManager#POLICY_SUSPEND_PACKAGES}.
+     */
+    private Set<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestrictionInternal(
+            int userId, @NonNull String restriction) {
+        Objects.requireNonNull(restriction);
+        Set<android.app.admin.EnforcingAdmin> admins = new HashSet<>();
+        // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as
+        // before the bug fix for b/192245204.
+        if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(
+                restriction)) {
+            ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+            if (profileOwner != null) {
+                EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        profileOwner, userId);
+                admins.add(admin.getParcelableAdmin());
+                return admins;
+            }
+            final Pair<Integer, ComponentName> deviceOwner =
+                    mOwners.getDeviceOwnerUserIdAndComponent();
+            if (deviceOwner != null && deviceOwner.first == userId) {
+                EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+                        deviceOwner.second, deviceOwner.first);
+                admins.add(admin.getParcelableAdmin());
+                return admins;
+            }
+        } else {
+            long ident = mInjector.binderClearCallingIdentity();
+            try {
+                PolicyDefinition<Boolean> policyDefinition = getPolicyDefinitionForRestriction(
+                        restriction);
+                Boolean value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+                if (value != null && value) {
+                    Map<EnforcingAdmin, PolicyValue<Boolean>> globalPolicies =
+                            mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition);
+                    for (EnforcingAdmin admin : globalPolicies.keySet()) {
+                        if (globalPolicies.get(admin) != null
+                                && Boolean.TRUE.equals(globalPolicies.get(admin).getValue())) {
+                            admins.add(admin.getParcelableAdmin());
+                        }
+                    }
+
+                    Map<EnforcingAdmin, PolicyValue<Boolean>> localPolicies =
+                            mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+                                    policyDefinition, userId);
+                    for (EnforcingAdmin admin : localPolicies.keySet()) {
+                        if (localPolicies.get(admin) != null
+                                && Boolean.TRUE.equals(localPolicies.get(admin).getValue())) {
+                            admins.add(admin.getParcelableAdmin());
+                        }
+                    }
+                    return admins;
+                }
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
+            }
+        }
+        return admins;
+    }
+
+    private static PolicyDefinition<Boolean> getPolicyDefinitionForRestriction(
+            @NonNull String restriction) {
+        Objects.requireNonNull(restriction);
+        if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+            return PolicyDefinition.getPolicyDefinitionForUserRestriction(
+                    UserManager.DISALLOW_CAMERA);
+        } else if (DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+            return PolicyDefinition.SCREEN_CAPTURE_DISABLED;
+        } else {
+            return PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction);
+        }
+    }
+
+
+    /**
      *  Excludes restrictions imposed by UserManager.
      */
     private List<UserManager.EnforcingUser> getDevicePolicySources(
@@ -15873,6 +15955,13 @@
         return getEnforcingAdminAndUserDetailsInternal(userId, restriction);
     }
 
+    @Override
+    public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction(
+            int userId, String restriction) {
+        Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+        return new ArrayList<>(getEnforcingAdminsForRestrictionInternal(userId, restriction));
+    }
+
     /**
      * @param restriction The restriction enforced by admin. It could be any user restriction or
      *                    policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5243d14..0066422 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -228,7 +228,8 @@
         return new android.app.admin.EnforcingAdmin(
                 mPackageName,
                 authority,
-                UserHandle.of(mUserId));
+                UserHandle.of(mUserId),
+                mComponentName);
     }
 
     /**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 82d00a6..7374901 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -44,6 +44,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.ThermalStatus;
 
 import org.junit.Before;
@@ -477,6 +478,23 @@
                 mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
     }
 
+    @Test
+    public void testHdrBrightnessDataFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData();
+
+        assertNotNull(data);
+        assertEquals(2, data.mMaxBrightnessLimits.size());
+        assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
+        assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+        assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
+        assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+
+        assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
+        assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
+    }
+
     private void verifyConfigValuesFromConfigResource() {
         assertNull(mDisplayDeviceConfig.getName());
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
@@ -694,6 +712,25 @@
                 + "</proxSensor>\n";
     }
 
+    private String getHdrBrightnessConfig() {
+        return "<hdrBrightnessConfig>\n"
+              + "    <brightnessMap>\n"
+              + "        <point>\n"
+              + "            <first>500</first>\n"
+              + "            <second>0.3</second>\n"
+              + "        </point>\n"
+              + "        <point>\n"
+              + "           <first>1200</first>\n"
+              + "           <second>0.6</second>\n"
+              + "        </point>\n"
+              + "    </brightnessMap>\n"
+              + "    <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
+              + "    <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+              + "    <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
+              + "    <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+              + "</hdrBrightnessConfig>";
+    }
+
     private String getContent() {
         return getContent(getValidLuxThrottling(), getValidProxSensor());
     }
@@ -784,6 +821,7 @@
                 +            "</point>\n"
                 +       "</sdrHdrRatioMap>\n"
                 +   "</highBrightnessMode>\n"
+                + getHdrBrightnessConfig()
                 + brightnessCapConfig
                 +   "<lightSensor>\n"
                 +       "<type>test_light_sensor</type>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index e7dc48e..89e28cb8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -43,6 +43,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
@@ -67,7 +68,9 @@
 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.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
 import com.android.server.policy.WindowManagerPolicy;
@@ -97,6 +100,7 @@
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
     private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
@@ -695,6 +699,80 @@
     }
 
     @Test
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+    }
+
+    @Test
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+    }
+
+    @Test
     public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
         // We should still set screen state for the default display
         DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1175,6 +1253,28 @@
         verify(mHolder.displayPowerState, times(1)).stop();
     }
 
+    @Test
+    public void testRampRateForHdrContent() {
+        float clampedBrightness = 0.6f;
+        float transitionRate = 35.5f;
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+        when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+        when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
+
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
+                eq(transitionRate));
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -1270,12 +1370,15 @@
         final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+        final HdrClamper hdrClamper = mock(HdrClamper.class);
+        final DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController, hbmController));
+                hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+                flags));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1289,11 +1392,11 @@
                 mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
+                hbmMetadata, /* bootCompleted= */ false, flags);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, wakelockController,
-                screenOffBrightnessSensorController, hbmController, hbmMetadata,
+                screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
                 brightnessMappingStrategy, injector);
     }
 
@@ -1311,6 +1414,8 @@
         public final WakelockController wakelockController;
         public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
         public final HighBrightnessModeController hbmController;
+
+        public final HdrClamper hdrClamper;
         public final HighBrightnessModeMetadata hbmMetadata;
         public final BrightnessMappingStrategy brightnessMappingStrategy;
         public final DisplayPowerController2.Injector injector;
@@ -1322,6 +1427,7 @@
                 WakelockController wakelockController,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController hbmController,
+                HdrClamper hdrClamper,
                 HighBrightnessModeMetadata hbmMetadata,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 DisplayPowerController2.Injector injector) {
@@ -1334,6 +1440,7 @@
             this.wakelockController = wakelockController;
             this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
             this.hbmController = hbmController;
+            this.hdrClamper = hdrClamper;
             this.hbmMetadata = hbmMetadata;
             this.brightnessMappingStrategy = brightnessMappingStrategy;
             this.injector = injector;
@@ -1350,13 +1457,19 @@
         private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
         private final HighBrightnessModeController mHighBrightnessModeController;
 
+        private final HdrClamper mHdrClamper;
+
+        private final DisplayManagerFlags mFlags;
+
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
                 AutomaticBrightnessController automaticBrightnessController,
                 WakelockController wakelockController,
                 BrightnessMappingStrategy brightnessMappingStrategy,
                 HysteresisLevels hysteresisLevels,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
-                HighBrightnessModeController highBrightnessModeController) {
+                HighBrightnessModeController highBrightnessModeController,
+                HdrClamper hdrClamper,
+                DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
             mAutomaticBrightnessController = automaticBrightnessController;
@@ -1365,6 +1478,8 @@
             mHysteresisLevels = hysteresisLevels;
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
+            mHdrClamper = hdrClamper;
+            mFlags = flags;
         }
 
         @Override
@@ -1471,6 +1586,15 @@
         }
 
         @Override
+        BrightnessRangeController getBrightnessRangeController(
+                HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags) {
+            return new BrightnessRangeController(hbmController, modeChangeCallback,
+                    displayDeviceConfig, mHdrClamper, mFlags);
+        }
+
+        @Override
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
                 SensorManager sensorManager, Resources resources) {
             return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e0c0ae2..971ece3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -43,6 +43,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.Handler;
@@ -69,6 +70,7 @@
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
 import com.android.server.policy.WindowManagerPolicy;
@@ -97,6 +99,7 @@
     private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
     private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
     private static final float PROX_SENSOR_MAX_RANGE = 5;
+    private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
     private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
     private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
@@ -1184,6 +1187,77 @@
         verify(mHolder.displayPowerState, times(1)).stop();
     }
 
+    @Test
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+    }
+
+    @Test
+    public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+        final float sdrBrightness = 0.1f;
+        final float hdrBrightness = 0.3f;
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+        when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+                any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+        when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+        when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+        when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+        when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+        clearInvocations(mHolder.animator);
+
+        mHolder.dpc.updateBrightness();
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+                eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+    }
+
     private void advanceTime(long timeMs) {
         mClock.fastForward(timeMs);
         mTestLooper.dispatchAll();
@@ -1282,6 +1356,7 @@
         final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
         final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
         final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+        final DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
 
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
 
@@ -1289,7 +1364,7 @@
                 mContext, injector, mDisplayPowerCallbacksMock, mHandler,
                 mSensorManagerMock, mDisplayBlankerMock, display,
                 mBrightnessTrackerMock, brightnessSetting, () -> {},
-                hbmMetadata, /* bootCompleted= */ false);
+                hbmMetadata, /* bootCompleted= */ false, flags);
 
         return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
                 animator, automaticBrightnessController, screenOffBrightnessSensorController,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java
new file mode 100644
index 0000000..2820da7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 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 org.junit.Assert.assertEquals;
+
+import android.util.FloatProperty;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class RampAnimatorTest {
+
+    private RampAnimator<TestObject> mRampAnimator;
+
+    private final TestObject mTestObject = new TestObject();
+
+    private final FloatProperty<TestObject> mTestProperty = new FloatProperty<>("mValue") {
+        @Override
+        public void setValue(TestObject object, float value) {
+            object.mValue = value;
+        }
+
+        @Override
+        public Float get(TestObject object) {
+            return object.mValue;
+        }
+    };
+
+    @Before
+    public void setUp() {
+        mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty);
+    }
+
+    @Test
+    public void testInitialValueUsedInLastAnimationStep() {
+        mRampAnimator.setAnimationTarget(0.67f, 0.1f);
+
+        assertEquals(0.67f, mTestObject.mValue, 0);
+    }
+
+    private static class TestObject {
+        private float mValue;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
new file mode 100644
index 0000000..0ebe46a
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 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.brightness.clamper;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+public class HdrClamperTest {
+
+    public static final float FLOAT_TOLERANCE = 0.0001f;
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private BrightnessClamperController.ClamperChangeListener mMockListener;
+
+    OffsettableClock mClock = new OffsettableClock.Stopped();
+
+    private final TestHandler mTestHandler = new TestHandler(null, mClock);
+
+
+    private HdrClamper mHdrClamper;
+
+
+    @Before
+    public void setUp() {
+        mHdrClamper = new HdrClamper(mMockListener, mTestHandler);
+        configureClamper();
+    }
+
+    @Test
+    public void testClamper_AmbientLuxChangesAboveLimit() {
+        mHdrClamper.onAmbientLuxChange(500);
+
+        assertFalse(mTestHandler.hasMessagesOrCallbacks());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testClamper_AmbientLuxChangesBelowLimit() {
+        mHdrClamper.onAmbientLuxChange(499);
+
+        assertTrue(mTestHandler.hasMessagesOrCallbacks());
+        TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+        assertEquals(2000, msgInfo.sendTime);
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+
+        mClock.fastForward(2000);
+        mTestHandler.timeAdvance();
+        assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testClamper_AmbientLuxChangesBelowLimit_ThenFastAboveLimit() {
+        mHdrClamper.onAmbientLuxChange(499);
+        mHdrClamper.onAmbientLuxChange(500);
+
+        assertFalse(mTestHandler.hasMessagesOrCallbacks());
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+    }
+
+    @Test
+    public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() {
+        mHdrClamper.onAmbientLuxChange(499);
+        mClock.fastForward(2000);
+        mTestHandler.timeAdvance();
+
+        mHdrClamper.onAmbientLuxChange(500);
+
+        assertTrue(mTestHandler.hasMessagesOrCallbacks());
+        TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+        assertEquals(3000, msgInfo.sendTime); // 2000 + 1000
+
+        mClock.fastForward(1000);
+        mTestHandler.timeAdvance();
+        assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+    }
+
+    private void configureClamper() {
+        mHdrClamper.getConfiguration().mMaxBrightnessLimits.put(500f, 0.6f);
+        mHdrClamper.getConfiguration().mIncreaseConfig.mDebounceTimeMillis = 1000;
+        mHdrClamper.getConfiguration().mIncreaseConfig.mTransitionTimeMillis = 1500;
+        mHdrClamper.getConfiguration().mDecreaseConfig.mDebounceTimeMillis = 2000;
+        mHdrClamper.getConfiguration().mDecreaseConfig.mTransitionTimeMillis = 2500;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 525bfd7..5b1508b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -16,7 +16,6 @@
 package com.android.server;
 
 import static androidx.test.InstrumentationRegistry.getContext;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -42,7 +41,6 @@
 import static com.android.server.DeviceIdleController.STATE_SENSING;
 import static com.android.server.DeviceIdleController.lightStateToString;
 import static com.android.server.DeviceIdleController.stateToString;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -2418,7 +2416,7 @@
     }
 
     @Test
-    public void testModeManager_NoModeManagerLocalService_AddListenerNotCalled() {
+    public void testModeManager_NoModeManagerLocalService_AddQuickDozeListenerNotCalled() {
         mConstants.USE_MODE_MANAGER = true;
         doReturn(null)
                 .when(() -> LocalServices.getService(WearModeManagerInternal.class));
@@ -2430,6 +2428,33 @@
     }
 
     @Test
+    public void testModeManager_NoModeManagerLocalService_AddOffBodyListenerNotCalled() {
+        mConstants.USE_MODE_MANAGER = true;
+        doReturn(null)
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.OFFBODY_STATE_ID), any(),
+                eq(mDeviceIdleController.mModeManagerOffBodyStateConsumer));
+    }
+
+    @Test
+    public void testModeManager_USEMODEMANAGERIsFalse_AddListenerNotCalled() {
+        mConstants.USE_MODE_MANAGER = false;
+        doReturn(new Object())
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.OFFBODY_STATE_ID), any(),
+                eq(mDeviceIdleController.mModeManagerOffBodyStateConsumer));
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER), any(),
+                eq(mDeviceIdleController.mModeManagerQuickDozeRequestConsumer));
+    }
+
+    @Test
     public void testModeManager_NoBatterySaver_QuickDoze() {
         mConstants.USE_MODE_MANAGER = true;
         PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
@@ -2469,6 +2494,102 @@
         assertTrue(mDeviceIdleController.isQuickDozeEnabled());
     }
 
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverEnabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverEnabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverDisabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeRequestedBatterySaverDisabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                false).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeNotRequestedBatterySaverEnabledOnBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(false);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
+    @Test
+    public void testModeManager_QuickDozeNotRequestedBatterySaverEnabledOffBody_QuickDozeEnabled() {
+        mConstants.USE_MODE_MANAGER = true;
+        PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
+                true).build();
+        when(mPowerManagerInternal.getLowPowerState(anyInt()))
+                .thenReturn(powerSaveState);
+        cleanupDeviceIdleController();
+        setupDeviceIdleController();
+
+        mDeviceIdleController.mModeManagerOffBodyStateConsumer.accept(true);
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
+
+        assertTrue(mDeviceIdleController.isQuickDozeEnabled());
+    }
+
     private void enterDeepState(int state) {
         switch (state) {
             case STATE_ACTIVE:
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index d988063..614fd11 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -18,7 +18,9 @@
 
 import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -157,11 +159,12 @@
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
                 mock(Resources.class));
-        when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);
 
         mArchiveManager = spy(new PackageArchiver(mContext, pm));
         doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId));
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+        doReturn(mIcon).when(mArchiveManager).decodeIcon(
+                any(ArchiveState.ArchiveActivityInfo.class));
     }
 
     @Test
@@ -249,7 +252,7 @@
     public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
         IOException e = new IOException("IO");
         doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
-                any(LauncherActivityInfo.class), eq(mUserId));
+                any(LauncherActivityInfo.class), eq(mUserId), anyInt());
 
         mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
@@ -372,6 +375,38 @@
         assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
     }
 
+    @Test
+    public void getArchivedAppIcon_packageNotInstalled() {
+        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+                null);
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s not found.", PACKAGE));
+    }
+
+    @Test
+    public void getArchivedAppIcon_notArchived() {
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s is not currently archived.", PACKAGE));
+    }
+
+    @Test
+    public void getArchivedAppIcon_success() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
+                mIcon);
+    }
+
+
     private static ArchiveState createArchiveState() {
         List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
         for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
new file mode 100644
index 0000000..9fdbdda
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:AnrTimerTest
+ */
+@SmallTest
+@Presubmit
+public class AnrTimerTest {
+
+    /**
+     * A handler that allows control over when to dispatch messages and callbacks. Because most
+     * Handler methods are final, the only thing this handler can intercept is sending messages.
+     * This handler allows unit tests to be written without a need to sleep (which leads to flaky
+     * tests).
+     *
+     * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}.
+     */
+    static class TestHandler extends Handler {
+
+        private boolean mImmediate = true;
+        private ArrayList<Message> mQueuedMessages = new ArrayList<>();
+
+        ArrayList<Long> mDelays = new ArrayList<>();
+
+        TestHandler(Looper looper, Callback callback, boolean immediate) {
+            super(looper, callback);
+            mImmediate = immediate;
+        }
+
+        TestHandler(Looper looper, Callback callback) {
+            this(looper, callback, true);
+        }
+
+        /**
+         * Override sendMessageAtTime.  In immediate mode, the message is immediately dispatched.
+         * In non-immediate mode, the message is enqueued to the real handler.  In both cases, the
+         * original delay is computed by comparing the target dispatch time with 'now'.  This
+         * computation is prone to errors if the code experiences delays.  The computed time is
+         * captured in the mDelays list.
+         */
+        @Override
+        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+            long delay = uptimeMillis - SystemClock.uptimeMillis();
+            mDelays.add(delay);
+            if (mImmediate) {
+                mQueuedMessages.add(msg);
+                dispatchQueuedMessages();
+            } else {
+                super.sendMessageAtTime(msg, uptimeMillis);
+            }
+            return true;
+        }
+
+        void setImmediate(boolean immediate) {
+            mImmediate = immediate;
+        }
+
+        /** Dispatch any messages that have been queued on the calling thread. */
+        void dispatchQueuedMessages() {
+            ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
+            mQueuedMessages.clear();
+            for (Message msg : messages) {
+                dispatchMessage(msg);
+            }
+        }
+
+        /**
+         * Compare the captured delays with the input array.  The comparison is fuzzy because the
+         * captured delay (see sendMessageAtTime) is affected by process delays.
+         */
+        void verifyDelays(long[] r) {
+            final long FUZZ = 10;
+            assertEquals(r.length, mDelays.size());
+            for (int i = 0; i < mDelays.size(); i++) {
+                long t = r[i];
+                long v = mDelays.get(i);
+                assertTrue(v >= t - FUZZ && v <= t + FUZZ);
+            }
+        }
+    }
+
+    private Handler mHandler;
+    private CountDownLatch mLatch = null;
+    private ArrayList<Message> mMessages;
+
+    // The commonly used message timeout key.
+    private static final int MSG_TIMEOUT = 1;
+
+    @Before
+    public void setUp() {
+        mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
+        mMessages = new ArrayList<>();
+        mLatch = new CountDownLatch(1);
+        AnrTimer.resetTimerListForHermeticTest();
+    }
+
+    @After
+    public void tearDown() {
+        mHandler = null;
+        mMessages = null;
+    }
+
+    // When a timer expires, set the expiration time in the message and add it to the queue.
+    private boolean expirationHandler(Message msg) {
+        mMessages.add(Message.obtain(msg));
+        mLatch.countDown();
+        return false;
+    }
+
+    // The test argument includes a pid and uid, and a tag.  The tag is used to distinguish
+    // different message instances.
+    private static class TestArg {
+        final int pid;
+        final int uid;
+        final int tag;
+
+        TestArg(int pid, int uid, int tag) {
+            this.pid = pid;
+            this.uid = uid;
+            this.tag = tag;
+        }
+        @Override
+        public String toString() {
+            return String.format("pid=%d uid=%d tag=%d", pid, uid, tag);
+        }
+    }
+
+    /**
+     * An instrumented AnrTimer.
+     */
+    private class TestAnrTimer extends AnrTimer {
+        // A local copy of 'what'.  The field in AnrTimer is private.
+        final int mWhat;
+
+        TestAnrTimer(Handler h, int key, String tag) {
+            super(h, key, tag);
+            mWhat = key;
+        }
+
+        TestAnrTimer() {
+            this(mHandler, MSG_TIMEOUT, caller());
+        }
+
+        TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) {
+            super(h, key, tag, extend, injector);
+            mWhat = key;
+        }
+
+        TestAnrTimer(boolean extend, TestInjector injector) {
+            this(mHandler, MSG_TIMEOUT, caller(), extend, injector);
+        }
+
+        // Return the name of method that called the constructor, assuming that this function is
+        // called from inside the constructor.  The calling method is used to name the AnrTimer
+        // instance so that logs are easier to understand.
+        private static String caller() {
+            final int n = 4;
+            StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+            if (stack.length < n+1) return "test";
+            return stack[n].getMethodName();
+        }
+
+        boolean start(TestArg arg, long millis) {
+            return start(arg, arg.pid, arg.uid, millis);
+        }
+
+        int what() {
+            return mWhat;
+        }
+    }
+
+    private static class TestTracker extends AnrTimer.CpuTracker {
+        long index = 0;
+        final int skip;
+        TestTracker(int skip) {
+            this.skip = skip;
+        }
+        long delay(int pid) {
+            return index++ * skip;
+        }
+    }
+
+    private class TestInjector extends AnrTimer.Injector {
+        final boolean mImmediate;
+        final AnrTimer.CpuTracker mTracker;
+        TestHandler mTestHandler;
+
+        TestInjector(int skip, boolean immediate) {
+            mTracker = new TestTracker(skip);
+            mImmediate = immediate;
+        }
+
+        TestInjector(int skip) {
+            this(skip, true);
+        }
+
+        @Override
+        Handler getHandler(Handler.Callback callback) {
+            if (mTestHandler == null) {
+                mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
+            }
+            return mTestHandler;
+        }
+
+        /** Fetch the allocated handle. This does not check for nulls. */
+        TestHandler getHandler() {
+            return mTestHandler;
+        }
+
+        AnrTimer.CpuTracker getTracker() {
+            return mTracker;
+        }
+    }
+
+    // Tests
+    // 1. Start a timer and wait for expiration.
+    // 2. Start a timer and cancel it.  Verify no expiration.
+    // 3. Start a timer.  Shortly thereafter, restart it.  Verify only one expiration.
+    // 4. Start a couple of timers.  Verify max active timers.  Discard one and verify the active
+    //    count drops by 1.  Accept one and verify the active count drops by 1.
+
+
+    @Test
+    public void testSimpleTimeout() throws Exception {
+        // Create an immediate TestHandler.
+        TestInjector injector = new TestInjector(0);
+        TestAnrTimer timer = new TestAnrTimer(false, injector);
+        TestArg t = new TestArg(1, 1, 3);
+        assertTrue(timer.start(t, 10));
+        // Delivery is immediate but occurs on a different thread.
+        assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+        assertEquals(1, mMessages.size());
+        Message m = mMessages.get(0);
+        assertEquals(timer.what(), m.what);
+        assertEquals(t, m.obj);
+
+        // Verify that the timer is still present.
+        assertEquals(1, AnrTimer.sizeOfTimerList());
+        assertTrue(timer.accept(t));
+        assertEquals(0, AnrTimer.sizeOfTimerList());
+
+        // Verify that the timer no longer exists.
+        assertFalse(timer.accept(t));
+    }
+
+    @Test
+    public void testCancel() throws Exception {
+        // Create an non-immediate TestHandler.
+        TestInjector injector = new TestInjector(0, false);
+        TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+        Handler handler = injector.getHandler();
+        assertNotNull(handler);
+        assertTrue(handler instanceof TestHandler);
+
+        // The tests that follow check for a 'what' of 0 (zero), which is the message key used
+        // by AnrTimer internally.
+        TestArg t = new TestArg(1, 1, 3);
+        assertFalse(handler.hasMessages(0));
+        assertTrue(timer.start(t, 100));
+        assertTrue(handler.hasMessages(0));
+        assertTrue(timer.cancel(t));
+        assertFalse(handler.hasMessages(0));
+
+        // Verify that no expiration messages were delivered.
+        assertEquals(0, mMessages.size());
+        assertEquals(0, AnrTimer.sizeOfTimerList());
+    }
+
+    @Test
+    public void testRestart() throws Exception {
+        // Create an non-immediate TestHandler.
+        TestInjector injector = new TestInjector(0, false);
+        TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+        TestArg t = new TestArg(1, 1, 3);
+        assertTrue(timer.start(t, 2500));
+        assertTrue(timer.start(t, 1000));
+
+        // Verify that the test handler saw two timeouts.
+        injector.getHandler().verifyDelays(new long[] { 2500, 1000 });
+
+        // Verify that there is a single timer.  Then cancel it.
+        assertEquals(1, AnrTimer.sizeOfTimerList());
+        assertTrue(timer.cancel(t));
+        assertEquals(0, AnrTimer.sizeOfTimerList());
+    }
+
+    @Test
+    public void testExtendNormal() throws Exception {
+        // Create an immediate TestHandler.
+        TestInjector injector = new TestInjector(5);
+        TestAnrTimer timer = new TestAnrTimer(true, injector);
+        TestArg t = new TestArg(1, 1, 3);
+        assertTrue(timer.start(t, 10));
+
+        assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+        assertEquals(1, mMessages.size());
+        Message m = mMessages.get(0);
+        assertEquals(timer.what(), m.what);
+        assertEquals(t, m.obj);
+
+        // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms.
+        injector.getHandler().verifyDelays(new long[] { 10, 5 });
+
+        // Verify that the timer is still present. Then remove it and verify that the list is
+        // empty.
+        assertEquals(1, AnrTimer.sizeOfTimerList());
+        assertTrue(timer.accept(t));
+        assertEquals(0, AnrTimer.sizeOfTimerList());
+    }
+
+    @Test
+    public void testExtendOversize() throws Exception {
+        // Create an immediate TestHandler.
+        TestInjector injector = new TestInjector(25);
+        TestAnrTimer timer = new TestAnrTimer(true, injector);
+        TestArg t = new TestArg(1, 1, 3);
+        assertTrue(timer.start(t, 10));
+
+        assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+        assertEquals(1, mMessages.size());
+        Message m = mMessages.get(0);
+        assertEquals(timer.what(), m.what);
+        assertEquals(t, m.obj);
+
+        // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms.
+        injector.getHandler().verifyDelays(new long[] { 10, 10 });
+
+        // Verify that the timer is still present. Then remove it and verify that the list is
+        // empty.
+        assertEquals(1, AnrTimer.sizeOfTimerList());
+        assertTrue(timer.accept(t));
+        assertEquals(0, AnrTimer.sizeOfTimerList());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
new file mode 100644
index 0000000..75d71da
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/BluetoothRouteControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BluetoothRouteControllerTest {
+
+    private final BluetoothRouteController.BluetoothRoutesUpdatedListener
+            mBluetoothRoutesUpdatedListener = routes -> {
+                // Empty on purpose.
+            };
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
+        BluetoothRouteController deviceRouteController =
+                BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener);
+
+        Truth.assertThat(deviceRouteController).isInstanceOf(LegacyBluetoothRouteController.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
+        BluetoothRouteController deviceRouteController =
+                BluetoothRouteController.createInstance(mContext, mBluetoothRoutesUpdatedListener);
+
+        Truth.assertThat(deviceRouteController)
+                .isInstanceOf(AudioPoliciesBluetoothRouteController.class);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
new file mode 100644
index 0000000..ec4b8a8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 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.media;
+
+import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+
+import android.content.Context;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DeviceRouteControllerTest {
+
+    private final DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener =
+            deviceRoute -> {
+                // Empty on purpose.
+            };
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    @RequiresFlagsDisabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public void createInstance_audioPoliciesFlagIsDisabled_createsLegacyController() {
+        DeviceRouteController deviceRouteController =
+                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+
+        Truth.assertThat(deviceRouteController).isInstanceOf(LegacyDeviceRouteController.class);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+    public void createInstance_audioPoliciesFlagIsEnabled_createsAudioPoliciesController() {
+        DeviceRouteController deviceRouteController =
+                DeviceRouteController.createInstance(mContext, mOnDeviceRouteChangedListener);
+
+        Truth.assertThat(deviceRouteController)
+                .isInstanceOf(AudioPoliciesDeviceRouteController.class);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index b0fd606..d85768d 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -79,6 +79,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
  * Tests for the {@link MediaProjectionManagerService} class.
  *
@@ -202,7 +205,8 @@
     }
 
     @Test
-    public void testCreateProjection_priorProjectionGrant() throws NameNotFoundException {
+    public void testCreateProjection_priorProjectionGrant() throws
+            NameNotFoundException, InterruptedException {
         // Create a first projection.
         MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
         FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback();
@@ -214,9 +218,13 @@
         FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback();
         secondProjection.start(callback2);
 
-        // Check that the second projection's callback hasn't been stopped.
-        assertThat(callback1.mStopped).isTrue();
-        assertThat(callback2.mStopped).isFalse();
+        // Check that the first projection get stopped, but not the second projection.
+        final int timeout = 5;
+        boolean stoppedCallback1 = callback1.mLatch.await(timeout, TimeUnit.SECONDS);
+        boolean stoppedCallback2 = callback2.mLatch.await(timeout, TimeUnit.SECONDS);
+
+        assertThat(stoppedCallback1).isTrue();
+        assertThat(stoppedCallback2).isFalse();
     }
 
     @Test
@@ -803,11 +811,10 @@
     }
 
     private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
-        boolean mStopped = false;
-
+        CountDownLatch mLatch = new CountDownLatch(1);
         @Override
         public void onStop() throws RemoteException {
-            mStopped = true;
+            mLatch.countDown();
         }
 
         @Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index b522cab..5147a08 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -28,6 +28,7 @@
 import static com.android.server.notification.NotificationRecordLogger.NotificationCancelledEvent.NOTIFICATION_CANCEL_USER_OTHER;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
 import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -48,6 +49,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
+
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -230,4 +233,12 @@
                 NotificationRecordLogger.NotificationCancelledEvent.fromCancelReason(
                         REASON_CANCEL, DISMISSAL_OTHER));
     }
+
+    @Test
+    public void testGetAgeInMinutes() {
+        long postTimeMs = Duration.ofMinutes(5).toMillis();
+        long whenMs = Duration.ofMinutes(2).toMillis();
+        int age = NotificationRecordLogger.getAgeInMinutes(postTimeMs, whenMs);
+        assertThat(age).isEqualTo(3);
+    }
 }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index d6a3877..42e3383 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -47,8 +47,6 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
-    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
-
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
@@ -106,11 +104,6 @@
             android:showWhenLocked="true"
             android:turnScreenOn="true" />
 
-        <activity android:name="android.app.Activity"
-            android:exported="true"
-            android:showWhenLocked="true"
-            android:turnScreenOn="true" />
-
         <activity
             android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
             android:exported="true">
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 4791946..0a7bb00 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -297,6 +297,7 @@
         mPhoneWindowManager.overrideUserSetupComplete();
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideTogglePanel();
+        mPhoneWindowManager.overrideInjectKeyEvent();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index e301da7..6d46d9c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -464,6 +464,10 @@
         doReturn(device).when(mInputManager).getInputDevice(anyInt());
     }
 
+    void overrideInjectKeyEvent() {
+        doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
+    }
+
     void overrideSearchKeyBehavior(int behavior) {
         mPhoneWindowManager.mSearchKeyBehavior = behavior;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 0989db4..568471d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
@@ -26,7 +25,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -36,7 +34,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -44,13 +41,11 @@
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
 import android.app.admin.DevicePolicyManagerInternal;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
@@ -76,7 +71,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -509,88 +503,4 @@
 
         verify(callback, times(1)).onActivityLaunched(any(), any(), any());
     }
-
-    @Test
-    public void testSandboxServiceInterceptionHappensToIntentWithSandboxActivityAction() {
-        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
-        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
-        PackageManager packageManagerMock = mock(PackageManager.class);
-        String sandboxPackageNameMock = "com.sandbox.mock";
-        when(mContext.getPackageManager()).thenReturn(packageManagerMock);
-        when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
-        Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        verify(spyCallback, times(1)).onInterceptActivityLaunch(
-                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
-    }
-
-    @Test
-    public void testSandboxServiceInterceptionHappensToIntentWithSandboxPackage() {
-        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
-        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
-        PackageManager packageManagerMock = mock(PackageManager.class);
-        String sandboxPackageNameMock = "com.sandbox.mock";
-        when(mContext.getPackageManager()).thenReturn(packageManagerMock);
-        when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
-        Intent intent = new Intent().setPackage(sandboxPackageNameMock);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        verify(spyCallback, times(1)).onInterceptActivityLaunch(
-                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
-    }
-
-    @Test
-    public void testSandboxServiceInterceptionHappensToIntentWithComponentNameWithSandboxPackage() {
-        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
-        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
-        PackageManager packageManagerMock = mock(PackageManager.class);
-        String sandboxPackageNameMock = "com.sandbox.mock";
-        when(mContext.getPackageManager()).thenReturn(packageManagerMock);
-        when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
-        Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        verify(spyCallback, times(1)).onInterceptActivityLaunch(
-                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
-    }
-
-    @Test
-    public void testSandboxServiceInterceptionNotCalledWhenIntentNotRelatedToSandbox() {
-        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
-        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
-        PackageManager packageManagerMock = mock(PackageManager.class);
-        String sandboxPackageNameMock = "com.sandbox.mock";
-        when(mContext.getPackageManager()).thenReturn(packageManagerMock);
-        when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
-        // Intent: null
-        mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        // Action: null, Package: null, ComponentName: null
-        Intent intent = new Intent();
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        // Wrong Action
-        intent = new Intent().setAction(Intent.ACTION_VIEW);
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        // Wrong Package
-        intent = new Intent().setPackage("Random");
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        // Wrong ComponentName's package
-        intent = new Intent().setComponent(new ComponentName("Random", ""));
-        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
-        verify(spyCallback, never()).onInterceptActivityLaunch(
-                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index ecd84e1..3d3531e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -304,6 +304,30 @@
                 anyFloat(), anyFloat());
     }
 
+    /**
+     * Test that resizing the output surface results in resizing the mirrored content to fit.
+     */
+    @Test
+    public void testOnConfigurationChanged_resizeSurface() {
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+
+        // Resize the output surface.
+        final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
+                Math.round(sSurfaceSize.y * 2));
+        doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
+                anyInt());
+        mContentRecorder.onConfigurationChanged(
+                mVirtualDisplayContent.getConfiguration().orientation);
+
+        // No resize is issued, only the initial transformations when we started recording.
+        verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+                anyFloat());
+        verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+
+    }
+
     @Test
     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
         mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index be3f01e..80e169d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -17,15 +17,14 @@
 package com.android.server.wm;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
-import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-import static junit.framework.Assert.assertEquals;
+
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -38,8 +37,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.window.flags.FakeFeatureFlagsImpl;
-
 import org.junit.Before;
 import org.junit.Test;
 
@@ -60,16 +57,12 @@
     private LetterboxConfiguration mLetterboxConfiguration;
     private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
 
-    private MutableFakeFeatureFlagsImpl mMutableFakeFeatureFlags;
-
-
     @Before
     public void setUp() throws Exception {
         mContext = getInstrumentation().getTargetContext();
-        mMutableFakeFeatureFlags = new MutableFakeFeatureFlagsImpl();
         mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
         mLetterboxConfiguration = new LetterboxConfiguration(mContext,
-                mLetterboxConfigurationPersister, mMutableFakeFeatureFlags);
+                mLetterboxConfigurationPersister);
     }
 
     @Test
@@ -99,22 +92,6 @@
     }
 
     @Test
-    public void test_whenFlagEnabled_wallpaperIsDefaultBackground() {
-        mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(true);
-        assertEquals(LETTERBOX_BACKGROUND_WALLPAPER,
-                mLetterboxConfiguration.getLetterboxBackgroundType());
-        assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount());
-    }
-
-    @Test
-    public void test_whenFlagDisabled_solidColorIsDefaultBackground() {
-        mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(false);
-        assertEquals(LETTERBOX_BACKGROUND_SOLID_COLOR,
-                mLetterboxConfiguration.getLetterboxBackgroundType());
-        assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount());
-    }
-
-    @Test
     public void test_whenMovedHorizontally_updatePositionAccordingly() {
         // Starting from center
         assertForHorizontalMove(
@@ -311,23 +288,4 @@
                 false /* forTabletopMode */,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
     }
-
-    private static class MutableFakeFeatureFlagsImpl extends FakeFeatureFlagsImpl {
-        private boolean mLetterboxBackgroundWallpaperFlag;
-        private int mInvocationCount;
-
-        public void setLetterboxBackgroundWallpaperFlag(boolean letterboxBackgroundWallpaperFlag) {
-            mLetterboxBackgroundWallpaperFlag = letterboxBackgroundWallpaperFlag;
-        }
-
-        @Override
-        public boolean letterboxBackgroundWallpaperFlag() {
-            mInvocationCount++;
-            return mLetterboxBackgroundWallpaperFlag;
-        }
-
-        int getInvocationCount() {
-            return mInvocationCount;
-        }
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 2ad9fa0..0566f46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -862,6 +862,39 @@
     }
 
     @Test
+    public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldEnableUserAspectRatioSettings());
+    }
+
+    @Test
+    public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertTrue(mController.shouldEnableUserAspectRatioSettings());
+    }
+
+    @Test
+    public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientaion_returnsFalse()
+            throws Exception {
+        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldEnableUserAspectRatioSettings());
+    }
+
+    @Test
     public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
             throws Exception {
         prepareActivityThatShouldApplyUserMinAspectRatioOverride();
@@ -898,13 +931,26 @@
         assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
     }
 
-    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientationreturnsFalse() {
+        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
+            boolean orientationRequest) {
         spyOn(mController);
-        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        doReturn(orientationRequest).when(
+                mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
         mDisplayContent.setIgnoreOrientationRequest(true);
         doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
     }
 
+    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+        prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true);
+    }
+
     private void prepareActivityThatShouldApplyUserFullscreenOverride() {
         spyOn(mController);
         doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1dd71e0..fb27d63 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -571,26 +571,28 @@
         final Task task = rootTask.getBottomMostTask();
         final ActivityRecord root = task.getTopNonFinishingActivity();
         spyOn(mWm.mLetterboxConfiguration);
-
-        // When device config flag is disabled the button is not enabled
-        doReturn(false).when(mWm.mLetterboxConfiguration)
-                .isUserAppAspectRatioSettingsEnabled();
-        doReturn(false).when(mWm.mLetterboxConfiguration)
-                .isTranslucentLetterboxingEnabled();
-        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
-
-        // The flag is enabled
-        doReturn(true).when(mWm.mLetterboxConfiguration)
-                .isUserAppAspectRatioSettingsEnabled();
         spyOn(root);
-        doReturn(task).when(root).getOrganizedTask();
-        // When the flag is enabled and the top activity is not in size compat mode.
+        spyOn(root.mLetterboxUiController);
+
+        doReturn(true).when(root.mLetterboxUiController)
+                .shouldEnableUserAspectRatioSettings();
         doReturn(false).when(root).inSizeCompatMode();
+        doReturn(task).when(root).getOrganizedTask();
+
+        // The button should be eligible to be displayed
         assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
 
+        // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
+        doReturn(false).when(root.mLetterboxUiController)
+                .shouldEnableUserAspectRatioSettings();
+        assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+        doReturn(true).when(root.mLetterboxUiController)
+                .shouldEnableUserAspectRatioSettings();
+
         // When in size compat mode the button is not enabled
         doReturn(true).when(root).inSizeCompatMode();
         assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+        doReturn(false).when(root).inSizeCompatMode();
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
deleted file mode 100644
index e8a847c..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2023 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;
-
-import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.BuildUtils;
-import android.server.wm.CtsWindowInfoUtils;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-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;
-
-@Presubmit
-public class TrustedOverlayTests {
-    private static final String TAG = "TrustedOverlayTests";
-    private static final long TIMEOUT_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
-    @Rule
-    public TestName mName = new TestName();
-
-    private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(
-            Activity.class);
-
-    private Instrumentation mInstrumentation;
-    private Activity mActivity;
-
-    @Before
-    public void setup() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mActivity = mActivityRule.launchActivity(null);
-    }
-
-    @Test
-    public void setTrustedOverlayInputWindow() throws InterruptedException {
-        assumeFalse(USE_SURFACE_TRUSTED_OVERLAY);
-        testTrustedOverlayChildHelper(false);
-    }
-
-    @Test
-    public void setTrustedOverlayChildLayer() throws InterruptedException {
-        assumeTrue(USE_SURFACE_TRUSTED_OVERLAY);
-        testTrustedOverlayChildHelper(true);
-    }
-
-    private void testTrustedOverlayChildHelper(boolean expectTrusted) throws InterruptedException {
-        IBinder[] tokens = new IBinder[2];
-        CountDownLatch hostTokenReady = new CountDownLatch(1);
-        mInstrumentation.runOnMainSync(() -> {
-            mActivity.getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY);
-            View rootView = mActivity.getWindow().getDecorView();
-            if (rootView.isAttachedToWindow()) {
-                tokens[0] = rootView.getWindowToken();
-                hostTokenReady.countDown();
-            } else {
-                rootView.getViewTreeObserver().addOnWindowAttachListener(
-                        new ViewTreeObserver.OnWindowAttachListener() {
-                            @Override
-                            public void onWindowAttached() {
-                                tokens[0] = rootView.getWindowToken();
-                                hostTokenReady.countDown();
-                            }
-
-                            @Override
-                            public void onWindowDetached() {
-                            }
-                        });
-            }
-        });
-
-        assertTrue("Failed to wait for host to get added",
-                hostTokenReady.await(TIMEOUT_S, TimeUnit.SECONDS));
-
-        mInstrumentation.runOnMainSync(() -> {
-            WindowManager wm = mActivity.getSystemService(WindowManager.class);
-
-            View childView = new View(mActivity) {
-                @Override
-                protected void onAttachedToWindow() {
-                    super.onAttachedToWindow();
-                    tokens[1] = getWindowToken();
-                }
-            };
-            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-            params.token = tokens[0];
-            params.type = TYPE_APPLICATION_PANEL;
-            wm.addView(childView, params);
-        });
-
-        boolean[] foundTrusted = new boolean[2];
-
-        CtsWindowInfoUtils.waitForWindowInfos(
-                windowInfos -> {
-                    for (var windowInfo : windowInfos) {
-                        if (windowInfo.windowToken == tokens[0]
-                                && windowInfo.isTrustedOverlay) {
-                            foundTrusted[0] = true;
-                        } else if (windowInfo.windowToken == tokens[1]
-                                && windowInfo.isTrustedOverlay) {
-                            foundTrusted[1] = true;
-                        }
-                    }
-                    return foundTrusted[0] && foundTrusted[1];
-                }, TIMEOUT_S, TimeUnit.SECONDS);
-
-        if (!foundTrusted[0] || !foundTrusted[1]) {
-            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
-        }
-
-        assertEquals("Failed to find parent window or was not marked trusted", expectTrusted,
-                foundTrusted[0]);
-        assertEquals("Failed to find child window or was not marked trusted", expectTrusted,
-                foundTrusted[1]);
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
index f173d66..c5dd447 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -18,16 +18,16 @@
 
 import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
 import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.platform.test.annotations.Presubmit;
-import android.util.Log;
+import android.server.wm.CtsWindowInfoUtils;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.TrustedPresentationThresholds;
 
+import androidx.annotation.GuardedBy;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 
 import com.android.server.wm.utils.CommonUtils;
@@ -36,9 +36,8 @@
 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;
 import java.util.function.Consumer;
 
 /**
@@ -53,6 +52,15 @@
 
     private static final float FRACTION_VISIBLE = 0.1f;
 
+    private final Object mResultsLock = new Object();
+    @GuardedBy("mResultsLock")
+    private boolean mResult;
+    @GuardedBy("mResultsLock")
+    private boolean mReceivedResults;
+
+    @Rule
+    public TestName mName = new TestName();
+
     @Rule
     public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
             TestActivity.class);
@@ -71,36 +79,32 @@
 
     @Test
     public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
-        boolean[] results = new boolean[1];
-        CountDownLatch receivedResults = new CountDownLatch(1);
         TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
                 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
                 Runnable::run, inTrustedPresentationState -> {
-                    Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
-                    results[0] = inTrustedPresentationState;
-                    receivedResults.countDown();
+                    synchronized (mResultsLock) {
+                        mResult = inTrustedPresentationState;
+                        mReceivedResults = true;
+                        mResultsLock.notify();
+                    }
                 });
         t.apply();
-
-        assertTrue("Timed out waiting for results",
-                receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        assertTrue(results[0]);
+        synchronized (mResultsLock) {
+            assertResults();
+        }
     }
 
     @Test
     public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
-        final Object resultsLock = new Object();
-        boolean[] results = new boolean[1];
-        boolean[] receivedResults = new boolean[1];
         TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
                 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
         Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
-            synchronized (resultsLock) {
-                results[0] = inTrustedPresentationState;
-                receivedResults[0] = true;
-                resultsLock.notify();
+            synchronized (mResultsLock) {
+                mResult = inTrustedPresentationState;
+                mReceivedResults = true;
+                mResultsLock.notify();
             }
         };
         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -108,34 +112,43 @@
                 Runnable::run, trustedPresentationCallback);
         t.apply();
 
-        synchronized (resultsLock) {
-            if (!receivedResults[0]) {
-                resultsLock.wait(WAIT_TIME_MS);
+        synchronized (mResultsLock) {
+            if (!mReceivedResults) {
+                mResultsLock.wait(WAIT_TIME_MS);
             }
-            // Make sure we received the results and not just timed out
-            assertTrue("Timed out waiting for results", receivedResults[0]);
-            assertTrue(results[0]);
-
+            assertResults();
             // reset the state
-            receivedResults[0] = false;
+            mReceivedResults = false;
         }
 
         mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
                 trustedPresentationCallback);
         t.apply();
 
-        synchronized (resultsLock) {
-            if (!receivedResults[0]) {
-                resultsLock.wait(WAIT_TIME_MS);
+        synchronized (mResultsLock) {
+            if (!mReceivedResults) {
+                mResultsLock.wait(WAIT_TIME_MS);
             }
             // Ensure we waited the full time and never received a notify on the result from the
             // callback.
-            assertFalse("Should never have received a callback", receivedResults[0]);
+            assertFalse("Should never have received a callback", mReceivedResults);
             // results shouldn't have changed.
-            assertTrue(results[0]);
+            assertTrue(mResult);
         }
     }
 
+    @GuardedBy("mResultsLock")
+    private void assertResults() throws InterruptedException {
+        mResultsLock.wait(WAIT_TIME_MS);
+
+        if (!mReceivedResults) {
+            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
+        }
+        // Make sure we received the results and not just timed out
+        assertTrue("Timed out waiting for results", mReceivedResults);
+        assertTrue(mResult);
+    }
+
     public static class TestActivity extends Activity {
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 3d78a1d..0a70a5f 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -27,9 +27,9 @@
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
 
-import android.app.AppOpsManager;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AppOpsManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.content.ComponentName;
@@ -50,6 +50,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
+import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetectionServiceFailure;
@@ -114,6 +115,9 @@
     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
 
+    private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+            SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
     /**
      * Indicates the {@link HotwordDetectionService} is created.
      */
@@ -680,7 +684,8 @@
             mIntent = intent;
             mDetectionServiceType = detectionServiceType;
             int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
-            if (mVisualQueryDetectionComponentName != null
+            if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED
+                    && mVisualQueryDetectionComponentName != null
                     && mHotwordDetectionComponentName != null) {
                 flags |= Context.BIND_SHARED_ISOLATED_PROCESS;
             }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 471acc1..6ba77da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -57,6 +57,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SharedMemory;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.service.voice.HotwordDetector;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -96,6 +97,8 @@
 
     /** The delay time for retrying to request DirectActions. */
     private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+    private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+            SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
 
     final boolean mValid;
 
@@ -715,7 +718,7 @@
         } else {
             verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
         }
-        if (!verifyProcessSharingLocked()) {
+        if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED && !verifyProcessSharingLocked()) {
             Slog.w(TAG, "Sandboxed detection service not in shared isolated process");
             throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService "
                     + "not in a shared isolated process. Please make sure to set "
@@ -914,6 +917,7 @@
         if (hotwordInfo == null || visualQueryInfo == null) {
             return true;
         }
+        // Enforce shared isolated option is used when VisualQueryDetectionservice is enabled
         return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0
                 && (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
     }
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a72f780..1f32c97 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -412,6 +412,15 @@
             "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
 
     /**
+     * Optional extra for incoming containing a long which specifies the time the
+     * call was answered by user. This value is in milliseconds.
+     * @hide
+     */
+    public static final String EXTRA_CALL_ANSWERED_TIME_MILLIS =
+            "android.telecom.extra.CALL_ANSWERED_TIME_MILLIS";
+
+
+    /**
      * Optional extra for incoming and outgoing calls containing a long which specifies the Epoch
      * time the call was created.
      * @hide
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 66f3bed..36a8fc1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4607,12 +4607,12 @@
      *
      * <p>Example:
      *
-     * <pre><code>
+     * <pre>{@code
      * <string-array name="carrier_service_name_array" num="2">
      *   <item value="Police"/>
      *   <item value="Ambulance"/>
      * </string-array>
-     * </code></pre>
+     * }</pre>
      */
     public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
 
@@ -4626,18 +4626,18 @@
      *
      * <ul>
      *   <li>The number of items in both the arrays are equal
-     *   <li>The item added in this key follows a specific format. Either it should be all numbers,
-     *       or "+" followed by all numbers.
+     *   <li>The item should contain dialable characters only which includes 0-9, -, *, #, (, ),
+     *       SPACE.
      * </ul>
      *
      * <p>Example:
      *
-     * <pre><code>
+     * <pre>{@code
      * <string-array name="carrier_service_number_array" num="2">
-     *   <item value="123"/>
-     *   <item value="+343"/>
+     *   <item value="*123"/>
+     *   <item value="+ (111) 111-111"/>
      * </string-array>
-     * </code></pre>
+     * }</pre>
      */
     public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY =
         "carrier_service_number_array";
@@ -10229,7 +10229,7 @@
         sDefaults.putInt(KEY_LTE_PLUS_THRESHOLD_BANDWIDTH_KHZ_INT, 20000);
         sDefaults.putInt(KEY_NR_ADVANCED_THRESHOLD_BANDWIDTH_KHZ_INT, 0);
         sDefaults.putBoolean(KEY_INCLUDE_LTE_FOR_NR_ADVANCED_THRESHOLD_BANDWIDTH_BOOL, false);
-        sDefaults.putBoolean(KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL, true);
+        sDefaults.putBoolean(KEY_RATCHET_NR_ADVANCED_BANDWIDTH_IF_RRC_IDLE_BOOL, false);
         sDefaults.putIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY,
                 new int[]{CARRIER_NR_AVAILABILITY_NSA, CARRIER_NR_AVAILABILITY_SA});
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index f1af68f..53f347e 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -71,13 +71,6 @@
      */
     public static final int INVALID = Integer.MAX_VALUE;
 
-    private static final int LTE_RSRP_THRESHOLDS_NUM = 4;
-
-    private static final int WCDMA_RSCP_THRESHOLDS_NUM = 4;
-
-    /* The type of signal measurement */
-    private static final String MEASUREMENT_TYPE_RSCP = "rscp";
-
     // Timestamp of SignalStrength since boot
     // Effectively final. Timestamp is set during construction of SignalStrength
     private long mTimestampMillis;
@@ -92,28 +85,9 @@
     CellSignalStrengthNr mNr;
 
     /**
-     * Create a new SignalStrength from a intent notifier Bundle
-     *
-     * This method may be used by external applications.
-     *
-     * @param m Bundle from intent notifier
-     * @return newly created SignalStrength
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage
-    public static SignalStrength newFromBundle(Bundle m) {
-        SignalStrength ret;
-        ret = new SignalStrength();
-        ret.setFromNotifierBundle(m);
-        return ret;
-    }
-
-    /**
      * This constructor is used to create SignalStrength with default
      * values.
      *
-     * @return newly created SignalStrength
      * @hide
      */
     @UnsupportedAppUsage
@@ -164,20 +138,20 @@
      * Returns a List of CellSignalStrength Components of this SignalStrength Report.
      *
      * Use this API to access underlying
-     * {@link android.telephony#CellSignalStrength CellSignalStrength} objects that provide more
+     * {@link android.telephony.CellSignalStrength CellSignalStrength} objects that provide more
      * granular information about the SignalStrength report. Only valid (non-empty)
      * CellSignalStrengths will be returned. The order of any returned elements is not guaranteed,
      * and the list may contain more than one instance of a CellSignalStrength type.
      *
      * @return a List of CellSignalStrength or an empty List if there are no valid measurements.
      *
-     * @see android.telephony#CellSignalStrength
-     * @see android.telephony#CellSignalStrengthNr
-     * @see android.telephony#CellSignalStrengthLte
-     * @see android.telephony#CellSignalStrengthTdscdma
-     * @see android.telephony#CellSignalStrengthWcdma
-     * @see android.telephony#CellSignalStrengthCdma
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrength
+     * @see android.telephony.CellSignalStrengthNr
+     * @see android.telephony.CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthTdscdma
+     * @see android.telephony.CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthGsm
      */
     @NonNull public List<CellSignalStrength> getCellSignalStrengths() {
         return getCellSignalStrengths(CellSignalStrength.class);
@@ -187,7 +161,7 @@
      * Returns a List of CellSignalStrength Components of this SignalStrength Report.
      *
      * Use this API to access underlying
-     * {@link android.telephony#CellSignalStrength CellSignalStrength} objects that provide more
+     * {@link android.telephony.CellSignalStrength CellSignalStrength} objects that provide more
      * granular information about the SignalStrength report. Only valid (non-empty)
      * CellSignalStrengths will be returned. The order of any returned elements is not guaranteed,
      * and the list may contain more than one instance of a CellSignalStrength type.
@@ -197,13 +171,13 @@
      *        return values.
      * @return a List of CellSignalStrength or an empty List if there are no valid measurements.
      *
-     * @see android.telephony#CellSignalStrength
-     * @see android.telephony#CellSignalStrengthNr
-     * @see android.telephony#CellSignalStrengthLte
-     * @see android.telephony#CellSignalStrengthTdscdma
-     * @see android.telephony#CellSignalStrengthWcdma
-     * @see android.telephony#CellSignalStrengthCdma
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrength
+     * @see android.telephony.CellSignalStrengthNr
+     * @see android.telephony.CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthTdscdma
+     * @see android.telephony.CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthGsm
      */
     @NonNull public <T extends CellSignalStrength> List<T> getCellSignalStrengths(
             @NonNull Class<T> clazz) {
@@ -319,7 +293,7 @@
      *
      */
     public static final @android.annotation.NonNull Parcelable.Creator<SignalStrength> CREATOR =
-            new Parcelable.Creator<SignalStrength>() {
+            new Parcelable.Creator<>() {
                 public SignalStrength createFromParcel(Parcel in) {
                     return new SignalStrength(in);
                 }
@@ -327,7 +301,7 @@
                 public SignalStrength[] newArray(int size) {
                     return new SignalStrength[size];
                 }
-    };
+            };
 
     /**
      * Get the GSM RSSI in ASU.
@@ -338,7 +312,7 @@
      *
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthGsm#getAsuLevel}.
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrengthGsm
      * @see android.telephony.SignalStrength#getCellSignalStrengths
      */
     @Deprecated
@@ -352,7 +326,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthGsm#getBitErrorRate}.
      *
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrengthGsm
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -368,7 +342,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getCdmaDbm}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -382,7 +356,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getCdmaEcio}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -398,7 +372,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getEvdoDbm}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -412,7 +386,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getEvdoEcio}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -426,7 +400,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getEvdoSnr}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      */
     @Deprecated
@@ -438,7 +412,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getRssi}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -452,7 +426,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getRsrp}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -466,7 +440,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getRsrq}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -480,7 +454,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getRssnr}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -494,7 +468,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getCqi}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -527,7 +501,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrength#getAsuLevel}. Because the levels vary by technology,
      *             this method is misleading and should not be used.
-     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.CellSignalStrength
      * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
@@ -543,7 +517,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrength#getDbm()}. Because the levels vary by technology,
      *             this method is misleading and should not be used.
-     * @see android.telephony#CellSignalStrength
+     * @see android.telephony.CellSignalStrength
      * @see android.telephony.SignalStrength#getCellSignalStrengths
      * @hide
      */
@@ -559,7 +533,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthGsm#getDbm}.
      *
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrengthGsm
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -575,7 +549,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthGsm#getLevel}.
      *
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrengthGsm
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -591,7 +565,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthGsm#getAsuLevel}.
      *
-     * @see android.telephony#CellSignalStrengthGsm
+     * @see android.telephony.CellSignalStrengthGsm
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -607,7 +581,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getLevel}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -625,7 +599,7 @@
      *             ASU for CDMA, the resultant value is Android-specific and is not recommended
      *             for use.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -641,7 +615,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthCdma#getEvdoLevel}.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -659,7 +633,7 @@
      *             ASU for EvDO, the resultant value is Android-specific and is not recommended
      *             for use.
      *
-     * @see android.telephony#CellSignalStrengthCdma
+     * @see android.telephony.CellSignalStrengthCdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -675,7 +649,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getDbm}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -691,7 +665,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getLevel}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -708,7 +682,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthLte#getAsuLevel}.
      *
-     * @see android.telephony#CellSignalStrengthLte
+     * @see android.telephony.CellSignalStrengthLte
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -739,7 +713,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthTdscdma#getDbm}.
      *
-     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.CellSignalStrengthTdscdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -758,7 +732,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthTdscdma#getLevel}.
      *
-     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.CellSignalStrengthTdscdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -774,7 +748,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthTdscdma#getAsuLevel}.
      *
-     * @see android.telephony#CellSignalStrengthTdscdma
+     * @see android.telephony.CellSignalStrengthTdscdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -790,7 +764,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthWcdma#getRscp}.
      *
-     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthWcdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -805,7 +779,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthWcdma#getAsuLevel}.
      *
-     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthWcdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -828,7 +802,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthWcdma#getDbm}.
      *
-     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthWcdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -843,7 +817,7 @@
      * @deprecated this information should be retrieved from
      *             {@link CellSignalStrengthWcdma#getDbm}.
      *
-     * @see android.telephony#CellSignalStrengthWcdma
+     * @see android.telephony.CellSignalStrengthWcdma
      * @see android.telephony.SignalStrength#getCellSignalStrengths()
      * @hide
      */
@@ -895,32 +869,12 @@
     }
 
     /**
-     * Set SignalStrength based on intent notifier map
-     *
-     * @param m intent notifier map
-     *
-     * @deprecated this method relies on non-stable implementation details, and full access to
-     *             internal storage is available via {@link getCellSignalStrengths()}.
-     * @hide
-     */
-    @Deprecated
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
-    private void setFromNotifierBundle(Bundle m) {
-        mCdma = m.getParcelable("Cdma", android.telephony.CellSignalStrengthCdma.class);
-        mGsm = m.getParcelable("Gsm", android.telephony.CellSignalStrengthGsm.class);
-        mWcdma = m.getParcelable("Wcdma", android.telephony.CellSignalStrengthWcdma.class);
-        mTdscdma = m.getParcelable("Tdscdma", android.telephony.CellSignalStrengthTdscdma.class);
-        mLte = m.getParcelable("Lte", android.telephony.CellSignalStrengthLte.class);
-        mNr = m.getParcelable("Nr", android.telephony.CellSignalStrengthNr.class);
-    }
-
-    /**
      * Set intent notifier Bundle based on SignalStrength
      *
      * @param m intent notifier Bundle
      *
      * @deprecated this method relies on non-stable implementation details, and full access to
-     *             internal storage is available via {@link getCellSignalStrengths()}.
+     *             internal storage is available via {@link #getCellSignalStrengths()}.
      * @hide
      */
     @Deprecated
diff --git a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
index de0ad59..0b75eb3 100644
--- a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
+++ b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "CtsSurfaceControlTestsStaging"
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 87231c8..93a5bf5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -27,7 +27,11 @@
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.utils.*
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
 import org.junit.FixMethodOrder
 import org.junit.Ignore
 import org.junit.Test
@@ -63,11 +67,12 @@
                     .withHomeActivityVisible()
                     .waitForAndVerify()
             startDisplayBounds =
-                    wmHelper.currentState.layerState.physicalDisplayBounds ?:
-                    error("Display not found")
+                    wmHelper.currentState.layerState.physicalDisplayBounds
+                        ?: error("Display not found")
         }
         transitions {
-            SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp)
+            SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp,
+                flicker.scenario.startRotation)
             SplitScreenUtils.waitForSplitComplete(wmHelper, testApp, secondaryApp)
         }
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
index 4941eea..3cae1c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -30,6 +30,7 @@
 import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
 
 @Ignore("Base Test Class")
 abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
@@ -46,7 +47,9 @@
         tapl.setExpectedRotation(rotation.value)
 
         testApp1.launchViaIntent(wmHelper)
+        ChangeDisplayOrientationRule.setRotation(rotation)
         testApp2.launchViaIntent(wmHelper)
+        ChangeDisplayOrientationRule.setRotation(rotation)
         tapl.launchedAppState.quickSwitchToPreviousApp()
         wmHelper
             .StateSyncBuilder()
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 96b685d..365e00e 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -26,6 +26,7 @@
         "androidx.test.runner",
         "androidx.test.uiautomator_uiautomator",
         "servicestests-utils",
+        "flag-junit",
         "frameworks-base-testutils",
         "hamcrest-library",
         "kotlin-test",
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
new file mode 100644
index 0000000..3a2a3be
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 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.hardware.input
+
+import android.content.ContextWrapper
+import android.graphics.drawable.Drawable
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.hardware.input.Flags
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for Keyboard layout preview
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardLayoutPreviewTests
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardLayoutPreviewTests {
+
+    companion object {
+        const val WIDTH = 100
+        const val HEIGHT = 100
+    }
+
+    @get:Rule
+    val setFlagsRule = SetFlagsRule()
+
+    private fun createDrawable(): Drawable? {
+        val context = ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())
+        val inputManager = context.getSystemService(InputManager::class.java)!!
+        return inputManager.getKeyboardLayoutPreview(null, WIDTH, HEIGHT)
+    }
+
+    @Test
+    fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
+        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+        val drawable = createDrawable()!!
+        assertEquals(WIDTH, drawable.intrinsicWidth)
+        assertEquals(HEIGHT, drawable.intrinsicHeight)
+    }
+
+    @Test
+    fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
+        setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+        assertNull(createDrawable())
+    }
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
new file mode 100644
index 0000000..eee486f
--- /dev/null
+++ b/tests/InputScreenshotTest/Android.bp
@@ -0,0 +1,60 @@
+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: "InputScreenshotTests",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    static_libs: [
+        "androidx.arch.core_core-testing",
+        "androidx.compose.ui_ui-test-junit4",
+        "androidx.compose.ui_ui-test-manifest",
+        "androidx.lifecycle_lifecycle-runtime-testing",
+        "androidx.compose.animation_animation",
+        "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-extended",
+        "androidx.compose.runtime_runtime",
+        "androidx.compose.runtime_runtime-livedata",
+        "androidx.compose.ui_ui-tooling-preview",
+        "androidx.lifecycle_lifecycle-livedata-ktx",
+        "androidx.lifecycle_lifecycle-runtime-compose",
+        "androidx.navigation_navigation-compose",
+        "truth-prebuilt",
+        "androidx.compose.runtime_runtime",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "androidx.test.uiautomator_uiautomator",
+        "servicestests-utils",
+        "frameworks-base-testutils",
+        "platform-screenshot-diff-core",
+        "hamcrest-library",
+        "kotlin-test",
+        "flag-junit",
+        "platform-test-annotations",
+        "services.core.unboosted",
+        "testables",
+        "testng",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+    ],
+    test_suites: ["device-tests"],
+    compile_multilib: "both",
+    use_embedded_native_libs: false,
+    asset_dirs: ["assets"],
+}
diff --git a/tests/InputScreenshotTest/AndroidManifest.xml b/tests/InputScreenshotTest/AndroidManifest.xml
new file mode 100644
index 0000000..9ffbb3a
--- /dev/null
+++ b/tests/InputScreenshotTest/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 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.input.screenshot">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Screenshot tests for Input"
+        android:targetPackage="com.android.input.screenshot">
+    </instrumentation>
+</manifest>
diff --git a/tests/InputScreenshotTest/AndroidTest.xml b/tests/InputScreenshotTest/AndroidTest.xml
new file mode 100644
index 0000000..cc25fa4
--- /dev/null
+++ b/tests/InputScreenshotTest/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 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.
+  -->
+<configuration description="Runs Input screendiff tests.">
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <option name="test-suite-tag" value="apct" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="optimized-property-setting" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="InputScreenshotTests.apk" />
+    </target_preparer>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys"
+                value="/data/user/0/com.android.input.screenshot/files/input_screenshots" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.input.screenshot" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/InputScreenshotTest/OWNERS b/tests/InputScreenshotTest/OWNERS
new file mode 100644
index 0000000..3cffce9
--- /dev/null
+++ b/tests/InputScreenshotTest/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 136048
+include /core/java/android/hardware/input/OWNERS
diff --git a/tests/InputScreenshotTest/TEST_MAPPING b/tests/InputScreenshotTest/TEST_MAPPING
new file mode 100644
index 0000000..727e609
--- /dev/null
+++ b/tests/InputScreenshotTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "InputScreenshotTests"
+    }
+  ]
+}
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 0000000..70e4a71
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
new file mode 100644
index 0000000..502c1b4
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
new file mode 100644
index 0000000..591b2fa
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..0137a85
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..37a91e1
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt
new file mode 100644
index 0000000..84c971c
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+    val bitmap =
+        Bitmap.createBitmap(
+                measuredWidth,
+            measuredHeight,
+            Bitmap.Config.ARGB_8888,
+        )
+    val canvas = Canvas(bitmap)
+    draw(canvas)
+    return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+    if (Build.CPU_ABI == "x86_64") {
+        // Different CPU architectures can sometimes end up rendering differently, so we can't do
+        // pixel-perfect matching on different architectures using the same golden. Given that our
+        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+        // x86_64 architecture and use the Structural Similarity Index on others.
+        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+        // do pixel perfect matching both at presubmit time and at development time with actual
+        // devices.
+        PixelPerfectMatcher()
+    } else {
+        MSSIMMatcher()
+    }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher()
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt
new file mode 100644
index 0000000..edddc6b4
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+
+/**
+ * The emulations specs for all 8 permutations of:
+ * - phone or tablet.
+ * - dark of light mode.
+ * - portrait or landscape.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletFull
+    get() = PhoneAndTabletFullSpec
+
+private val PhoneAndTabletFullSpec =
+        DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
+
+/**
+ * The emulations specs of:
+ * - phone + light mode + portrait.
+ * - phone + light mode + landscape.
+ * - tablet + dark mode + portrait.
+ *
+ * This allows to test the most important permutations of a screen/layout with only 3
+ * configurations.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
+    get() = PhoneAndTabletMinimalSpec
+
+private val PhoneAndTabletMinimalSpec =
+    DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
+    DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
+
+/**
+ * This allows to test only single most important configuration.
+ */
+val DeviceEmulationSpec.Companion.PhoneMinimal
+    get() = PhoneMinimalSpec
+
+private val PhoneMinimalSpec =
+    DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false, isLandscape = false)
+
+object Displays {
+    val Phone =
+        DisplaySpec(
+            "phone",
+            width = 1440,
+            height = 3120,
+            densityDpi = 560,
+        )
+
+    val Tablet =
+        DisplaySpec(
+            "tablet",
+            width = 2560,
+            height = 1600,
+            densityDpi = 320,
+        )
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
new file mode 100644
index 0000000..8faf224
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
+class InputGoldenImagePathManager(
+        pathConfig: PathConfig,
+        assetsPathRelativeToBuildRoot: String
+) :
+        GoldenImagePathManager(
+                appContext = InstrumentationRegistry.getInstrumentation().context,
+                assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+                deviceLocalPath =
+                    InstrumentationRegistry.getInstrumentation()
+                        .targetContext
+                        .filesDir
+                        .absolutePath
+                        .toString() + "/input_screenshots",
+                pathConfig = pathConfig,
+        ) {
+    override fun toString(): String {
+        // This string is appended to all actual/expected screenshots on the device, so make sure
+        // it is a static value.
+        return "InputGoldenImagePathManager"
+    }
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
new file mode 100644
index 0000000..c2c3d55
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import android.content.Context
+import android.graphics.Bitmap
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.Image
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.graphics.asImageBitmap
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Input screenshot diff tests. */
+class InputScreenshotTestRule(
+        emulationSpec: DeviceEmulationSpec,
+        assetsPathRelativeToBuildRoot: String
+) : TestRule {
+    private val colorsRule = MaterialYouColorsRule()
+    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+    private val screenshotRule =
+        ScreenshotTestRule(
+            InputGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetsPathRelativeToBuildRoot
+            )
+        )
+    private val composeRule = createAndroidComposeRule<ComponentActivity>()
+    private val delegateRule =
+            RuleChain.outerRule(colorsRule)
+                .around(deviceEmulationRule)
+                .around(screenshotRule)
+                .around(composeRule)
+    private val matcher = UnitTestBitmapMatcher
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return delegateRule.apply(base, description)
+    }
+
+    /**
+     * Compare [content] with the golden image identified by [goldenIdentifier].
+     */
+    fun screenshotTest(
+            goldenIdentifier: String,
+            content: (Context) -> Bitmap,
+    ) {
+        // Make sure that the activity draws full screen and fits the whole display.
+        val activity = composeRule.activity
+        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+        // correctly.
+        composeRule.setContent {
+            Image(
+                bitmap = content(activity).asImageBitmap(),
+                contentDescription = null,
+            )
+        }
+        composeRule.waitForIdle()
+
+        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+    }
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
new file mode 100644
index 0000000..e855786
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for Ansi physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewAnsiScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
+    }
+
+    val setFlagsRule = SetFlagsRule()
+    val screenshotRule = InputScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/tests/InputScreenshotTest/assets"
+    )
+
+    @get:Rule
+    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+    @Test
+    fun test() {
+        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+        screenshotRule.screenshotTest("layout-preview-ansi") {
+            context: Context -> LayoutPreview.createLayoutPreview(
+                context,
+                KeyboardLayout(
+                    "descriptor",
+                    "layout",
+                    /* collection= */null,
+                    /* priority= */0,
+                    LocaleList(Locale.US),
+                    /* layoutType= */0,
+                    /* vid= */0,
+                    /* pid= */0
+                )
+            )
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
new file mode 100644
index 0000000..8ae6dfd
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for Iso physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+    }
+
+    val setFlagsRule = SetFlagsRule()
+    val screenshotRule = InputScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/tests/InputScreenshotTest/assets"
+    )
+
+    @get:Rule
+    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+    @Test
+    fun test() {
+        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+        screenshotRule.screenshotTest("layout-preview") {
+            context: Context -> LayoutPreview.createLayoutPreview(context, null)
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
new file mode 100644
index 0000000..5231c14
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 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.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for JIS physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewJisScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
+    }
+
+    val setFlagsRule = SetFlagsRule()
+    val screenshotRule = InputScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/tests/InputScreenshotTest/assets"
+    )
+
+    @get:Rule
+    val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+    @Test
+    fun test() {
+        setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+        screenshotRule.screenshotTest("layout-preview-jis") {
+            context: Context -> LayoutPreview.createLayoutPreview(
+                context,
+                KeyboardLayout(
+                    "descriptor",
+                    "layout",
+                    /* collection= */null,
+                    /* priority= */0,
+                    LocaleList(Locale.JAPAN),
+                    /* layoutType= */0,
+                    /* vid= */0,
+                    /* pid= */0
+                )
+            )
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt
new file mode 100644
index 0000000..76ee379
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 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.
+ */
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.hardware.input.InputManager
+import android.hardware.input.KeyboardLayout
+import android.util.TypedValue
+import kotlin.math.roundToInt
+
+object LayoutPreview {
+    fun createLayoutPreview(context: Context, layout: KeyboardLayout?): Bitmap {
+        val im = context.getSystemService(InputManager::class.java)!!
+        val width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                600.0F, context.getResources().getDisplayMetrics()).roundToInt()
+        val height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                200.0F, context.getResources().getDisplayMetrics()).roundToInt()
+        val drawable = im.getKeyboardLayoutPreview(layout, width, height)!!
+        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bitmap)
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight())
+        drawable.draw(canvas)
+        return bitmap
+    }
+}
\ No newline at end of file
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 4f655e5..55bab7d 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,4 @@
 set noparent
-toddke@google.com
 zyy@google.com
-patb@google.com
\ No newline at end of file
+patb@google.com
+markpun@google.com