Merge "Unify the default behaviour path for solid color ss" 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/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/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/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/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 94c3b52..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;
 
@@ -833,4 +834,6 @@
     void unregisterPackageMonitorCallback(IRemoteCallback callback);
 
     ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
+
+    Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
 }
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/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/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/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/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/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 05063365..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;
         }
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/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/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/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/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 5821537..9bf3ce4 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -225,8 +225,4 @@
     <bool name="telephony_analytics_switch">true</bool>
     <java-symbol type="bool" name="telephony_analytics_switch" />
 
-    <!-- Whether to enable modem on boot if behavior is not defined -->
-    <bool name="config_enable_cellular_on_boot_default">true</bool>
-    <java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
-
 </resources>
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/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 0e1a6b7..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;
 
@@ -881,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/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/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/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/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 e916a14..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) {
@@ -389,6 +395,9 @@
             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,
@@ -528,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");
@@ -581,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
@@ -750,8 +764,17 @@
                         final boolean wasClosing = closingIdx >= 0;
                         t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
                         t.setLayer(target.leash, layer);
-                        // Hide the animation leash if not already visible, let listener show it
-                        t.setVisibility(target.leash, !wasClosing);
+                        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 wasClosing=%b",
                                 target.taskId, wasClosing);
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/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/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/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/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/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/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/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/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/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
index 7abf9ae..173d57b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -26,7 +26,6 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    androidprv:layout_maxWidth="@dimen/keyguard_security_width"
     android:layout_gravity="center_horizontal|bottom"
     android:gravity="bottom">
 
@@ -36,6 +35,7 @@
         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"
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_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/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/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index bfd43dc..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;
@@ -259,8 +257,6 @@
                         mFalsingCollector, mKeyguardViewController,
                         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 d30f497..8d5fc04 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -158,7 +158,8 @@
         if (mIsLockScreenLandscapeEnabled) {
             boolean useSplitBouncerAfterFold =
                     mLastDevicePosture == DEVICE_POSTURE_CLOSED
-                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+                    && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+                    && getResources().getBoolean(R.bool.update_bouncer_constraints);
 
             if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
                 updateConstraints(useSplitBouncerAfterFold);
@@ -170,20 +171,14 @@
     @Override
     protected void updateConstraints(boolean useSplitBouncer) {
         mAlreadyUsingSplitBouncer = useSplitBouncer;
-        KeyguardSecurityViewFlipper.LayoutParams params =
-                (KeyguardSecurityViewFlipper.LayoutParams) getLayoutParams();
-
         if (useSplitBouncer) {
-            if (mContainerMotionLayout == null) return;
             mContainerMotionLayout.jumpToState(R.id.split_constraints);
-            params.maxWidth = Integer.MAX_VALUE;
+            mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
         } else {
             mContainerMotionLayout.jumpToState(R.id.single_constraints);
-            params.maxWidth = getResources()
-                    .getDimensionPixelSize(R.dimen.keyguard_security_width);
+            mContainerMotionLayout.setMaxWidth(getResources()
+                    .getDimensionPixelSize(R.dimen.keyguard_security_width));
         }
-
-        setLayoutParams(params);
     }
 
     @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 5528837..f18504c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -155,7 +155,7 @@
     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_motion_layout;
             case SimPin: return R.layout.keyguard_sim_pin_view;
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 be28ded..4d713a2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -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,8 +773,7 @@
 
     // 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
 
@@ -804,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/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/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 15a0972..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;
@@ -381,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;
@@ -1035,7 +1033,6 @@
     @VisibleForTesting
     void onFinishInflate() {
         loadDimens();
-        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
 
         FrameLayout userAvatarContainer = null;
         KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -1053,7 +1050,7 @@
 
         mKeyguardStatusBarViewController =
                 mKeyguardStatusBarViewComponentFactory.build(
-                                mKeyguardStatusBar,
+                                mView.findViewById(R.id.keyguard_header),
                                 mShadeViewStateProvider)
                         .getKeyguardStatusBarViewController();
         mKeyguardStatusBarViewController.init();
@@ -1226,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
@@ -1257,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();
@@ -1842,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 =
@@ -3730,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
@@ -4891,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;
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/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/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/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/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/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/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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4c70db8..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());
-                        });
                     }
                 }
             }
@@ -12088,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());
-                        });
                     }
                 }
 
@@ -12734,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());
-                    });
                 }
             }
 
@@ -15145,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.
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/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5b71595..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;
@@ -1077,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,
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 64d2314..21273e0 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -19,10 +19,9 @@
 import android.hardware.display.BrightnessInfo;
 import android.os.Handler;
 import android.os.IBinder;
-import android.provider.DeviceConfigInterface;
 
 import com.android.server.display.brightness.clamper.HdrClamper;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
 import java.util.function.BooleanSupplier;
@@ -42,19 +41,19 @@
 
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
-            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler) {
+            Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
+            DisplayManagerFlags flags) {
         this(hbmController, modeChangeCallback, displayDeviceConfig,
-                new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())),
-                new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
+                new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags);
     }
 
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
-            HdrClamper hdrClamper, DeviceConfigParameterProvider configParameterProvider) {
+            HdrClamper hdrClamper, DisplayManagerFlags flags) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
-        mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
         mUseHdrClamper = false;
+        mUseNbmController = flags.isNbmControllerEnabled();
         mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
         mHdrClamper = hdrClamper;
     }
@@ -64,7 +63,6 @@
         pw.println("  mUseNormalBrightnessController=" + mUseNbmController);
         mHbmController.dump(pw);
         mNormalBrightnessModeController.dump(pw);
-
     }
 
     void onAmbientLuxChange(float ambientLux) {
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 90c7ce7..540ddd2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -136,7 +136,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;
@@ -2668,42 +2667,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) {
@@ -3267,12 +3230,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;
@@ -4471,16 +4434,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 b7b46ea..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, mHandler);
+                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 7021eed..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();
@@ -540,7 +541,7 @@
         mBrightnessThrottler = createBrightnessThrottlerLocked();
 
         mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
-                modeChangeCallback, mDisplayDeviceConfig, mHandler);
+                modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
 
         mDisplayBrightnessController =
                 new DisplayBrightnessController(context, null,
@@ -1523,8 +1524,25 @@
             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) {
@@ -2977,9 +2995,10 @@
 
         BrightnessRangeController getBrightnessRangeController(
                 HighBrightnessModeController hbmController, Runnable modeChangeCallback,
-                DisplayDeviceConfig displayDeviceConfig, Handler handler) {
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags) {
             return new BrightnessRangeController(hbmController,
-                    modeChangeCallback, displayDeviceConfig, handler);
+                    modeChangeCallback, displayDeviceConfig, handler, flags);
         }
 
         DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler 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/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/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/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 515c7fb..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(
@@ -951,9 +952,6 @@
                     throw new SecurityException("Media projections require a foreground service"
                             + " of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION");
                 }
-
-                mCallback = callback;
-                registerCallback(mCallback);
                 try {
                     mToken = callback.asBinder();
                     mDeathEater = () -> {
@@ -998,6 +996,11 @@
                     }
                 }
                 startProjectionLocked(this);
+
+                // Register new callbacks after stop has been dispatched to previous session.
+                mCallback = callback;
+                registerCallback(mCallback);
+
                 // Mark this token as used when the app gets the MediaProjection instance.
                 mCountStarts++;
             }
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/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index ff347ac..a4ee3c8 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -22,6 +22,8 @@
 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;
@@ -57,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 {
@@ -148,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();
@@ -658,6 +664,11 @@
         return mUpdateBroadcastInstantUserIds;
     }
 
+    @NonNull
+    public ArrayList<String> getWarnings() {
+        return mWarnings;
+    }
+
     public void setScanFlags(int scanFlags) {
         mScanFlags = scanFlags;
     }
@@ -855,6 +866,10 @@
         }
     }
 
+    public void addWarning(@NonNull String warning) {
+        mWarnings.add(warning);
+    }
+
     public void onPrepareStarted() {
         if (mPackageMetrics != null) {
             mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE);
@@ -904,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 551b1ae..e8be748 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -307,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);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 512d338..d699baa 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -5138,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 b5b6ce0..700fae9 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1433,6 +1433,9 @@
                 break;
             }
         }
+        if (!request.getWarnings().isEmpty()) {
+            extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings());
+        }
         return extras;
     }
 
@@ -6389,6 +6392,11 @@
             return getArchivedPackageInternal(packageName, userId);
         }
 
+        @Override
+        public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+            return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
+        }
+
         /**
          * Wait for the handler to finish handling all pending messages.
          * @param timeoutMillis Maximum time in milliseconds to wait.
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1b30c4b..72a7370 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4397,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/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/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 00992a0..4c525e9 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3749,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 5e0aa38..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.
@@ -9183,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/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/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 77e1f5b..561848e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8407,6 +8407,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;
@@ -8868,6 +8879,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;
@@ -8887,8 +8903,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 4979274..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;
@@ -471,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,
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 11ff42b..89e28cb8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -70,7 +70,7 @@
 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.DeviceConfigParameterProvider;
+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;
@@ -100,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;
@@ -698,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();
@@ -1185,6 +1260,8 @@
 
         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);
@@ -1294,15 +1371,14 @@
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
         final HdrClamper hdrClamper = mock(HdrClamper.class);
-        final DeviceConfigParameterProvider deviceConfigParameterProvider =
-                mock(DeviceConfigParameterProvider.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, hdrClamper,
-                deviceConfigParameterProvider));
+                flags));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
         final DisplayDevice device = mock(DisplayDevice.class);
@@ -1316,7 +1392,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, wakelockController,
@@ -1383,7 +1459,7 @@
 
         private final HdrClamper mHdrClamper;
 
-        private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
+        private final DisplayManagerFlags mFlags;
 
         TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
                 AutomaticBrightnessController automaticBrightnessController,
@@ -1393,7 +1469,7 @@
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 HdrClamper hdrClamper,
-                DeviceConfigParameterProvider deviceConfigParameterProvider) {
+                DisplayManagerFlags flags) {
             mDisplayPowerState = dps;
             mAnimator = animator;
             mAutomaticBrightnessController = automaticBrightnessController;
@@ -1403,7 +1479,7 @@
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mHdrClamper = hdrClamper;
-            mDeviceConfigParameterProvider = deviceConfigParameterProvider;
+            mFlags = flags;
         }
 
         @Override
@@ -1512,9 +1588,10 @@
         @Override
         BrightnessRangeController getBrightnessRangeController(
                 HighBrightnessModeController hbmController, Runnable modeChangeCallback,
-                DisplayDeviceConfig displayDeviceConfig, Handler handler) {
+                DisplayDeviceConfig displayDeviceConfig, Handler handler,
+                DisplayManagerFlags flags) {
             return new BrightnessRangeController(hbmController, modeChangeCallback,
-                    displayDeviceConfig, mHdrClamper, mDeviceConfigParameterProvider);
+                    displayDeviceConfig, mHdrClamper, mFlags);
         }
 
         @Override
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/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 5555c19..614fd11 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -159,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), anyInt());
+        doReturn(mIcon).when(mArchiveManager).decodeIcon(
+                any(ArchiveState.ArchiveActivityInfo.class));
     }
 
     @Test
@@ -374,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/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index ddd1221..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,6 +205,29 @@
     }
 
     @Test
+    public void testCreateProjection_priorProjectionGrant() throws
+            NameNotFoundException, InterruptedException {
+        // Create a first projection.
+        MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+        FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback();
+        projection.start(callback1);
+
+        // Create a second projection.
+        MediaProjectionManagerService.MediaProjection secondProjection =
+                startProjectionPreconditions();
+        FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback();
+        secondProjection.start(callback2);
+
+        // 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
     public void testCreateProjection_attemptReuse_noPriorProjectionGrant()
             throws NameNotFoundException {
         // Create a first projection.
@@ -785,8 +811,10 @@
     }
 
     private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
+        CountDownLatch mLatch = new CountDownLatch(1);
         @Override
         public void onStop() throws RemoteException {
+            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/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/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/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